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:
Bohdan Triapitsyn
2026-08-22 01:10:19 +03:00
parent 9f7d839fc6
commit 0b01f5ae2d
24 changed files with 1582 additions and 38 deletions
+18
View File
@@ -157,6 +157,22 @@ export interface GetGitRangeDiffOptions {
contextLines?: number;
}
export interface GetGitRangeFilesOptions {
base: string;
head: string;
}
/** One changed file in a `base...head` range, with its change letter (A/M/D/R/C). */
export interface GitRangeFileEntry {
path: string;
status: string;
}
export interface GitBranchBaseResponse {
/** Null when git has no authoritative record of where the branch started. */
base: string | null;
}
export interface GitFileDiffResponse {
original: string;
modified: string;
@@ -466,6 +482,8 @@ export interface GitAPI {
getGitDiff(directory: string, options: GetGitDiffOptions): Promise<GitDiffResponse>;
getGitFileDiff(directory: string, options: GetGitFileDiffOptions): Promise<GitFileDiffResponse>;
getGitRangeDiff?(directory: string, options: GetGitRangeDiffOptions): Promise<GitDiffResponse>;
getGitRangeFiles?(directory: string, options: GetGitRangeFilesOptions): Promise<GitRangeFileEntry[]>;
getBranchBase?(directory: string, branch: string): Promise<GitBranchBaseResponse>;
revertGitFile(directory: string, filePath: string, options?: { scope?: 'all' | 'working' }): Promise<void>;
stageGitFile(directory: string, filePath: string): Promise<void>;
stageGitFiles?(directory: string, filePaths: string[]): Promise<void>;