feat(diff): add branch scope to context panel diff view
Show every change on the current branch relative to its base in the Changed/Staged/Last turn dropdown. The base comes from the branch's reflog record or an explicit per-branch user choice (persisted), never a main/master guess; when git has no record the user picks a base once from a searchable branch list. - server: GET /api/git/branch-base (reflog-derived base), GET /api/git/range-files (name-status -z with rename/copy destination paths and -C copy detection) - shared UI: optional getBranchBase/getGitRangeFiles runtime APIs with boundary parsing; persisted per-branch overrides keyed by runtime+directory+branch - DiffView: branch scope with confirmed-unavailability coercion of persisted tabs (detached HEAD, default-branch checkout, metadata settled without a default), range-invalidated diff cache guarded against stale completions, bounded branch-metadata retry, read-only diff actions in branch scope; hidden in VS Code - helper module branchDiffScope.ts with tests for coercion, availability, race conditions, and retry exhaustion
This commit is contained in:
@@ -3,6 +3,7 @@ import type {
|
||||
GitDiffResponse,
|
||||
GetGitDiffOptions,
|
||||
GetGitRangeDiffOptions,
|
||||
GetGitRangeFilesOptions,
|
||||
GitFileDiffResponse,
|
||||
GetGitFileDiffOptions,
|
||||
GitBranch,
|
||||
@@ -248,6 +249,51 @@ export async function getGitRangeDiff(
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function getGitRangeFiles(
|
||||
directory: string,
|
||||
options: GetGitRangeFilesOptions
|
||||
): Promise<import('./api/types').GitRangeFileEntry[]> {
|
||||
const { base, head } = options;
|
||||
if (!base || !head) {
|
||||
throw new Error('base and head are required to fetch git range files');
|
||||
}
|
||||
|
||||
const response = await runtimeFetch(
|
||||
buildUrl(`${API_BASE}/range-files`, directory, { base, head })
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to get git range files: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as { files?: unknown };
|
||||
if (!Array.isArray(payload.files)) return [];
|
||||
return payload.files.filter((entry): entry is import('./api/types').GitRangeFileEntry => {
|
||||
if (!entry || typeof entry !== 'object') return false;
|
||||
const candidate = entry as { path?: unknown; status?: unknown };
|
||||
return typeof candidate.path === 'string' && typeof candidate.status === 'string';
|
||||
});
|
||||
}
|
||||
|
||||
export async function getBranchBase(
|
||||
directory: string,
|
||||
branch: string
|
||||
): Promise<import('./api/types').GitBranchBaseResponse> {
|
||||
if (!branch) {
|
||||
throw new Error('branch is required to get branch base');
|
||||
}
|
||||
|
||||
const response = await runtimeFetch(
|
||||
buildUrl(`${API_BASE}/branch-base`, directory, { branch })
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to get branch base: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function getGitFileDiff(directory: string, options: GetGitFileDiffOptions): Promise<GitFileDiffResponse> {
|
||||
const { path, staged } = options;
|
||||
if (!path) {
|
||||
|
||||
Reference in New Issue
Block a user