feat: git diff panel (Files/Git tab in the file browser)
Adds a Git tab to the right-side file panel that shows the project repository's diff and lets the user stage, unstage, commit, and discard whole files in-session. Two comparison modes (Uncommitted vs HEAD, and the branch vs its base — upstream tracking branch else default branch), auto- selected by repo state on first open and pinned after explicit choice; per-file expand/collapse with lazy syntax-highlighted diffs, +/- stats, and binary/large-file placeholders. All git read and write logic lives in apps/server via a new git_diff service: argv-safe execFile only (never a shell), per-file paths validated repo-relative through pathGuard with a realpath symlink-escape check, server-derived commit identity (the request carries no author fields), and the write endpoints are deliberately absent from the assistant tool registry. Reads are bounded (30s deadline, 10MB); an index lock or an in-progress merge/rebase/cherry-pick/bisect surfaces as "repository busy" and disables writes. The panel stays current via a client git_diff_refresh session event (no new wire contract) coalesced across tab open, mutations, turn completion, and pending-change apply. Discard is an irrecoverable hard-delete behind a plain confirm that distinguishes reverting a tracked file from deleting an untracked one.
This commit is contained in:
@@ -10,6 +10,9 @@ import type {
|
||||
ViewFileResult,
|
||||
AgentsResponse,
|
||||
GitMeta,
|
||||
GitDiffMode,
|
||||
GitDiffResult,
|
||||
GitDiscardFileInfo,
|
||||
Skill,
|
||||
ToolCostStat,
|
||||
ProviderSnapshotEntry,
|
||||
@@ -151,6 +154,32 @@ export const api = {
|
||||
request<{ files: string[] }>(`/api/projects/${id}/files`),
|
||||
git: (id: string) =>
|
||||
request<GitMeta>(`/api/projects/${id}/git`),
|
||||
gitDiff: (id: string, mode: GitDiffMode | null) =>
|
||||
request<GitDiffResult>(
|
||||
mode !== null
|
||||
? `/api/projects/${id}/git/diff?mode=${mode}`
|
||||
: `/api/projects/${id}/git/diff`,
|
||||
),
|
||||
gitStage: (id: string, files: string[]) =>
|
||||
request<{ ok: boolean }>(`/api/projects/${id}/git/stage`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ files }),
|
||||
}),
|
||||
gitUnstage: (id: string, files: string[]) =>
|
||||
request<{ ok: boolean }>(`/api/projects/${id}/git/unstage`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ files }),
|
||||
}),
|
||||
gitCommit: (id: string, body: { message: string; files?: string[] }) =>
|
||||
request<{ ok: boolean }>(`/api/projects/${id}/git/commit`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
gitDiscard: (id: string, files: GitDiscardFileInfo[]) =>
|
||||
request<{ ok: boolean }>(`/api/projects/${id}/git/discard`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ files }),
|
||||
}),
|
||||
},
|
||||
|
||||
sessions: {
|
||||
|
||||
@@ -312,6 +312,39 @@ export interface GitMeta {
|
||||
behind: number;
|
||||
}
|
||||
|
||||
// git-diff-panel Phase 1: shapes returned by GET /api/projects/:id/git/diff.
|
||||
export type GitDiffMode = 'uncommitted' | 'committed';
|
||||
export type GitDiffChangeType = 'added' | 'modified' | 'deleted' | 'renamed' | 'untracked';
|
||||
|
||||
export interface GitDiffFile {
|
||||
path: string;
|
||||
old_path: string | null;
|
||||
change_type: GitDiffChangeType;
|
||||
added_lines: number;
|
||||
removed_lines: number;
|
||||
staged: boolean;
|
||||
diff_body: string | null;
|
||||
is_binary: boolean;
|
||||
is_too_large: boolean;
|
||||
}
|
||||
|
||||
export interface GitDiffResult {
|
||||
git_repo: boolean;
|
||||
mode: GitDiffMode;
|
||||
/** Server-computed mode based on dirty state — used for auto-select (FIX 1) and mode suggestion (FIX 4). */
|
||||
auto_mode?: GitDiffMode;
|
||||
base_label: string | null;
|
||||
in_progress_op: string | null;
|
||||
files: GitDiffFile[];
|
||||
}
|
||||
|
||||
// git-diff-panel Phase 2: per-file info for the discard endpoint.
|
||||
export interface GitDiscardFileInfo {
|
||||
path: string;
|
||||
change_type: GitDiffChangeType;
|
||||
staged: boolean;
|
||||
}
|
||||
|
||||
// Batch 9.6: skill catalog row. Returned by GET /api/skills and consumed by
|
||||
// the slash-command dropdown. `path` and `mtime` are exposed for debug surface
|
||||
// (/api/skills) but the dropdown only renders name + description.
|
||||
|
||||
Reference in New Issue
Block a user