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
1 change: 1 addition & 0 deletions packages/coc-client/src/contracts/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface GitBranchRangeInfo {

export interface GitDefaultBranchResponse {
onDefaultBranch: true;
branchName?: string;
}

export type GitBranchRangeResponse = GitBranchRangeInfo | GitDefaultBranchResponse;
Expand Down
6 changes: 4 additions & 2 deletions packages/coc/src/server/routes/api-git-branch-range-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ export function registerGitBranchRangeRoutes(ctx: ApiRouteContext): void {
const rangeService = getGitRangeService();
const range = await rangeService.detectCommitRange(ws.rootPath);
if (!range) {
const result = { onDefaultBranch: true };
const branchName = await rangeService.getCurrentBranch(ws.rootPath);
const result = { onDefaultBranch: true as const, branchName };
gitCache.set(cacheKey, result);
return sendJSON(res, 200, result);
}
Expand All @@ -75,7 +76,8 @@ export function registerGitBranchRangeRoutes(ctx: ApiRouteContext): void {
gitCache.set(cacheKey, result);
sendJSON(res, 200, result);
} catch {
sendJSON(res, 200, { onDefaultBranch: true });
const branchName = await getGitRangeService().getCurrentBranch(ws.rootPath).catch(() => 'HEAD');
sendJSON(res, 200, { onDefaultBranch: true, branchName });
}
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,13 @@ export function RepoGitTab({ workspaceId }: RepoGitTabProps) {
setOnDefaultBranch(true);
setBranchRangeData(null);
setBranchRangeFiles([]);
setBranchName(data.branchName || data.defaultBranch || 'main');
const resolvedBranchName = data.branchName || data.defaultBranch || '';
setBranchName(resolvedBranchName);
setAhead(0);
setBehind(0);
setBranchRangeCache(workspaceId, {
data: null, files: [], ahead: 0, behind: 0,
branchName: data.branchName || data.defaultBranch || 'main',
branchName: resolvedBranchName,
onDefaultBranch: true,
});
return null as BranchRangeInfo | null;
Expand Down Expand Up @@ -520,11 +521,16 @@ export function RepoGitTab({ workspaceId }: RepoGitTabProps) {
gitChangedDebounceRef.current = null;
// Snapshot current commits before the refresh overwrites them
prevCommitsRef.current = commits;
// Re-fetch commits and working tree but NOT branch range (cached)
fetchCommits(true, 0, searchQuery).then((newCommits: GitCommitItem[]) => {
// Clear stale branch range cache so the next fetch returns current branch
clearBranchRangeCache(workspaceId);
// Re-fetch commits, working tree, and branch range
Promise.all([
fetchCommits(true, 0, searchQuery),
fetchBranchRange(false),
]).then(([newCommits]) => {
setLastRefreshedAt(Date.now());
// Heuristic rebind: match old→new commits by identity
const pairs = matchCommitsByIdentity(prevCommitsRef.current, newCommits);
const pairs = matchCommitsByIdentity(prevCommitsRef.current, newCommits as GitCommitItem[]);
for (const { oldHash, newHash } of pairs) {
rebindCommitChat(workspaceId, oldHash, newHash);
}
Expand All @@ -546,7 +552,7 @@ export function RepoGitTab({ workspaceId }: RepoGitTabProps) {
.catch(() => {});
}
}
}, [workspaceId, commits, fetchCommits, searchQuery]),
}, [workspaceId, commits, fetchCommits, fetchBranchRange, searchQuery]),
});

// Pull job polling helpers
Expand Down
13 changes: 9 additions & 4 deletions packages/coc/test/server/git-branch-range-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { gitCache } from '../../src/server/git/git-cache';
const mockDetectCommitRange = vi.fn();
const mockGetRangeDiff = vi.fn();
const mockGetFileDiff = vi.fn();
const mockGetCurrentBranch = vi.fn().mockResolvedValue('feature/foo');

const mockExecSync = vi.fn();
vi.mock('child_process', function () { return ({
Expand All @@ -41,6 +42,7 @@ vi.mock('@plusplusoneplusplus/forge', async (importOriginal) => {
detectCommitRange: mockDetectCommitRange,
getRangeDiff: mockGetRangeDiff,
getFileDiff: mockGetFileDiff,
getCurrentBranch: mockGetCurrentBranch,
}); }),
};
});
Expand Down Expand Up @@ -161,6 +163,7 @@ describe('Git Branch Range API endpoints', () => {
mockGetRangeDiff.mockReset();
mockGetFileDiff.mockReset();
mockExecSync.mockReset();
mockGetCurrentBranch.mockResolvedValue('feature/foo');
gitCache.clear();
});

Expand All @@ -184,20 +187,22 @@ describe('Git Branch Range API endpoints', () => {
expect(data.deletions).toBe(2);
});

it('returns onDefaultBranch when detectCommitRange returns null', async () => {
it('returns onDefaultBranch with branchName when detectCommitRange returns null', async () => {
mockDetectCommitRange.mockReturnValue(null);
mockGetCurrentBranch.mockResolvedValue('pr/some-feature-branch');

const res = await request(`${base()}/api/workspaces/${WORKSPACE_ID}/git/branch-range`);
expect(res.status).toBe(200);
expect(res.json()).toEqual({ onDefaultBranch: true });
expect(res.json()).toEqual({ onDefaultBranch: true, branchName: 'pr/some-feature-branch' });
});

it('returns onDefaultBranch on git error', async () => {
it('returns onDefaultBranch with branchName on git error', async () => {
mockDetectCommitRange.mockImplementation(() => { throw new Error('not a git repo'); });
mockGetCurrentBranch.mockResolvedValue('my-branch');

const res = await request(`${base()}/api/workspaces/${WORKSPACE_ID}/git/branch-range`);
expect(res.status).toBe(200);
expect(res.json()).toEqual({ onDefaultBranch: true });
expect(res.json()).toEqual({ onDefaultBranch: true, branchName: 'my-branch' });
});

it('returns 404 for unknown workspace', async () => {
Expand Down
5 changes: 2 additions & 3 deletions packages/coc/test/server/git-branch-range-edge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ vi.mock('@plusplusoneplusplus/forge', async (importOriginal) => {
detectCommitRange: mockDetectCommitRange,
getRangeDiff: mockGetRangeDiff,
getFileDiff: mockGetFileDiff,
getCurrentBranch: vi.fn().mockReturnValue('main'),
getCurrentBranch: vi.fn().mockResolvedValue('main'),
}); }),
};
Expand Down Expand Up @@ -163,7 +162,7 @@ describe('Git Branch Range Edge Cases', () => {
);

expect(res.status).toBe(200);
expect(res.json()).toEqual({ onDefaultBranch: true });
expect(res.json()).toEqual({ onDefaultBranch: true, branchName: 'main' });
});

it('returns onDefaultBranch when base ref does not exist', async () => {
Expand All @@ -178,7 +177,7 @@ describe('Git Branch Range Edge Cases', () => {

// API returns onDefaultBranch on any git error (documented behavior)
expect(res.status).toBe(200);
expect(res.json()).toEqual({ onDefaultBranch: true });
expect(res.json()).toEqual({ onDefaultBranch: true, branchName: 'main' });
});
});

Expand Down
Loading