Skip to content

Commit ea801f1

Browse files
add path to /api/commits api
1 parent b4500ea commit ea801f1

File tree

4 files changed

+49
-9
lines changed

4 files changed

+49
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- Added `find_symbol_definitions`, and `find_symbol_references` tools to the MCP server. [#1014](https://github.com/sourcebot-dev/sourcebot/pull/1014)
1212
- Added `list_tree` tool to the ask agent. [#1014](https://github.com/sourcebot-dev/sourcebot/pull/1014)
1313
- Added input & output token breakdown in ask details card. [#1014](https://github.com/sourcebot-dev/sourcebot/pull/1014)
14+
- Added `path` parameter to the `/api/commits` api to allow filtering commits by paths. [#1014](https://github.com/sourcebot-dev/sourcebot/pull/1014)
1415

1516
### Fixed
1617
- Fixed issue where ask responses would sometimes appear in the details panel while generating. [#1014](https://github.com/sourcebot-dev/sourcebot/pull/1014)

packages/web/src/app/api/(server)/commits/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const listCommitsQueryParamsSchema = z.object({
1313
until: z.string().optional(),
1414
author: z.string().optional(),
1515
ref: z.string().optional(),
16+
path: z.string().optional(),
1617
page: z.coerce.number().int().positive().default(1),
1718
perPage: z.coerce.number().int().positive().max(100).default(50),
1819
});
@@ -57,6 +58,7 @@ export const GET = apiHandler(async (request: NextRequest): Promise<Response> =>
5758
...(searchParams.until ? { until: searchParams.until } : {}),
5859
...(searchParams.author ? { author: searchParams.author } : {}),
5960
...(searchParams.ref ? { ref: searchParams.ref } : {}),
61+
...(searchParams.path ? { path: searchParams.path } : {}),
6062
},
6163
});
6264
if (linkHeader) headers.set('Link', linkHeader);

packages/web/src/features/git/listCommitsApi.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type ListCommitsRequest = {
2828
until?: string;
2929
author?: string;
3030
ref?: string;
31+
path?: string;
3132
maxCount?: number;
3233
skip?: number;
3334
}
@@ -46,6 +47,7 @@ export const listCommits = async ({
4647
until,
4748
author,
4849
ref = 'HEAD',
50+
path,
4951
maxCount = 50,
5052
skip = 0,
5153
}: ListCommitsRequest): Promise<SearchCommitsResult | ServiceError> => sew(() =>
@@ -93,19 +95,31 @@ export const listCommits = async ({
9395
} : {}),
9496
};
9597

98+
// Build args array directly to ensure correct ordering:
99+
// git log [flags] <ref> [-- <path>]
100+
const logArgs: string[] = [`--max-count=${maxCount}`];
101+
if (skip > 0) {
102+
logArgs.push(`--skip=${skip}`);
103+
}
104+
for (const [key, value] of Object.entries(sharedOptions)) {
105+
logArgs.push(value !== null ? `${key}=${value}` : key);
106+
}
107+
logArgs.push(ref);
108+
if (path) {
109+
logArgs.push('--', path);
110+
}
111+
96112
// First, get the commits
97-
const log = await git.log({
98-
[ref]: null,
99-
maxCount,
100-
...(skip > 0 ? { '--skip': skip } : {}),
101-
...sharedOptions,
102-
});
113+
const log = await git.log(logArgs);
103114

104115
// Then, use rev-list to get the total count of commits
105116
const countArgs = ['rev-list', '--count', ref];
106117
for (const [key, value] of Object.entries(sharedOptions)) {
107118
countArgs.push(value !== null ? `${key}=${value}` : key);
108119
}
120+
if (path) {
121+
countArgs.push('--', path);
122+
}
109123

110124
const totalCount = parseInt((await git.raw(countArgs)).trim(), 10);
111125

packages/web/src/features/tools/listCommits.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { listCommits, SearchCommitsResult } from "@/features/git";
44
import { ToolDefinition } from "./types";
55
import { logger } from "./logger";
66
import description from "./listCommits.txt";
7+
import { CodeHostType } from "@sourcebot/db";
8+
import { getRepoInfoByName } from "@/actions";
79

810
const listCommitsShape = {
911
repo: z.string().describe("The repository to list commits from"),
@@ -12,11 +14,21 @@ const listCommitsShape = {
1214
until: z.string().describe("End date for commit range (e.g., 'yesterday', '2024-12-31', 'today')").optional(),
1315
author: z.string().describe("Filter commits by author name or email (case-insensitive)").optional(),
1416
ref: z.string().describe("Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch.").optional(),
17+
path: z.string().describe("Filter commits to only those that touched this file or directory path (relative to repo root).").optional(),
1518
page: z.number().int().positive().describe("Page number for pagination (min 1). Default: 1").optional().default(1),
1619
perPage: z.number().int().positive().max(100).describe("Results per page for pagination (min 1, max 100). Default: 50").optional().default(50),
1720
};
1821

19-
export type ListCommitsMetadata = SearchCommitsResult;
22+
export type ListCommitsRepoInfo = {
23+
name: string;
24+
displayName: string;
25+
codeHostType: CodeHostType;
26+
};
27+
28+
export type ListCommitsMetadata = SearchCommitsResult & {
29+
repo: string;
30+
repoInfo: ListCommitsRepoInfo;
31+
};
2032

2133
export const listCommitsDefinition: ToolDefinition<"list_commits", typeof listCommitsShape, ListCommitsMetadata> = {
2234
name: "list_commits",
@@ -28,7 +40,7 @@ export const listCommitsDefinition: ToolDefinition<"list_commits", typeof listCo
2840
execute: async (params, _context) => {
2941
logger.debug('list_commits', params);
3042

31-
const { repo, query, since, until, author, ref, page, perPage } = params;
43+
const { repo, query, since, until, author, ref, path, page, perPage } = params;
3244
const skip = (page - 1) * perPage;
3345

3446
const response = await listCommits({
@@ -38,6 +50,7 @@ export const listCommitsDefinition: ToolDefinition<"list_commits", typeof listCo
3850
until,
3951
author,
4052
ref,
53+
path,
4154
maxCount: perPage,
4255
skip,
4356
});
@@ -46,9 +59,19 @@ export const listCommitsDefinition: ToolDefinition<"list_commits", typeof listCo
4659
throw new Error(response.message);
4760
}
4861

62+
const repoInfoResult = await getRepoInfoByName(repo);
63+
if (isServiceError(repoInfoResult) || !repoInfoResult) {
64+
throw new Error(`Repository "${repo}" not found.`);
65+
}
66+
const repoInfo: ListCommitsRepoInfo = {
67+
name: repoInfoResult.name,
68+
displayName: repoInfoResult.displayName ?? repoInfoResult.name,
69+
codeHostType: repoInfoResult.codeHostType,
70+
};
71+
4972
return {
5073
output: JSON.stringify(response),
51-
metadata: response,
74+
metadata: { ...response, repo, repoInfo },
5275
};
5376
},
5477
};

0 commit comments

Comments
 (0)