/** * dsh-ui-tweaks — Git backend (host half). * * Executes read-only and mutating git commands in a session's working * directory (the "current project", resolved from the session header's cwd) * for the GitBar: status snapshot (branch, ahead/behind, changed files with * ±line counts), branch enumeration, per-file diff views, and the commit / * push / switch / create actions. * * Every command runs through `child_process.execFile` — never a shell, args * as an argv array, cwd pinned, bounded timeout, abort propagation — so * repo-controlled strings cannot escape into a shell and a hung remote cannot * wedge the plugin. * @module dsh-ui-tweaks/git */ import type { Context } from '@deepseek-ai/cordis'; /** One changed file with its ±line counts. */ export interface GitFileChange { path: string; /** Display status: M / A / D / R / C / U (untracked). */ status: string; added: number; deleted: number; untracked: boolean; } /** Full status snapshot for one session cwd. */ export interface GitSnapshot { isRepo: boolean; /** Absolute working directory, when resolvable. */ cwd?: string; /** Current branch name, or null when detached. */ branch: string | null; /** Short HEAD sha while detached. */ detachedHead?: string; /** Upstream ref name (e.g. `origin/main`), when tracked. */ upstream?: string; /** Commits ahead of the upstream. */ ahead: number; /** Commits behind the upstream. */ behind: number; hasRemote: boolean; clean: boolean; files: GitFileChange[]; totalAdded: number; totalDeleted: number; } /** Local + remote branch enumeration. */ export interface GitBranches { current: string | null; local: string[]; remote: string[]; } /** One tag: its name plus the commit it points at. */ export interface GitTag { name: string; /** Short hash of the tagged commit. */ hash: string; /** Subject line of the tagged commit. */ subject: string; } /** Tag enumeration, newest creation date first. */ export interface GitTags { tags: GitTag[]; } /** One commit row of the graph table. */ export interface GitGraphCommit { /** Full hashes of the parent commits, first parent first (empty at root commits). */ parents: string[]; /** Full commit hash. */ fullHash: string; /** Short hash (7 chars). */ hash: string; /** Subject line. */ subject: string; /** Author name. */ author: string; /** ISO-8601 commit date. */ date: string; /** Relative date, e.g. "3 days ago". */ dateRelative: string; /** Ref decorations, e.g. `HEAD -> main, origin/main` (outer parens stripped). */ refs: string; } /** Git commit graph table (structured rows across all refs; the client lays * out the branch lanes and draws the fork/merge SVG itself from `parents`). */ export interface GitGraph { commits: GitGraphCommit[]; /** Whether the requested commit window was cut off by the limit. */ truncated: boolean; } /** One rendered line of a per-file diff view. */ export interface DiffLine { type: 'hunk' | 'add' | 'del' | 'ctx'; /** Old-file line number (null for added lines). */ old: number | null; /** New-file line number (null for deleted lines). */ new: number | null; /** Content after the +/-/space prefix; hunk lines carry the full `@@` header. */ text: string; } /** Structured per-file diff for the GitBar side panel. */ export interface GitDiffResult { path: string; /** `hunk` = changed ranges only; `full` = whole file with added lines marked. */ mode: 'hunk' | 'full'; lines: DiffLine[]; truncated: boolean; } /** * The Git service: resolves a session's cwd and runs git on its behalf. * @param ctx - plugin context (sessions is read structurally, never required). */ export declare class GitBackend { private readonly ctx; constructor(ctx: Context); /** Resolve the session's working directory, or undefined when absent. */ resolveCwd(sessionId: string | undefined): string | undefined; /** * Resolve a workspace title (the hero/new-session screen has no real session * yet, only the picked workspace) to its directory. Matches the display * title exactly first, then a basename suffix — titles may duplicate, so an * exact hit always wins. Optional-service read like resolveCwd. */ resolveWorkspaceCwd(name: string | undefined): string | undefined; /** Resolve either target form to a cwd (session id first, then workspace title). */ resolveTargetCwd(target: { session?: string | undefined; ws?: string | undefined; }): string | undefined; /** Full status snapshot for a session's cwd. One merged `status -z --branch * --untracked-files=all` call carries branch/upstream/tracking and the * per-file untracked list (no ls-files expansion pass), and every read — * repo probe included — runs in one parallel batch. The previous nine * sequential process spawns cost ~700ms on Windows, this costs one spawn. */ snapshot(cwd: string, signal?: AbortSignal): Promise; /** Local + remote branch enumeration. */ branches(cwd: string, signal?: AbortSignal): Promise; /** * Per-file diff for the side panel. `mode: 'hunk'` returns only the changed * ranges (untracked files render as whole-file additions); `mode: 'full'` * returns the whole file with added lines marked. */ diff(cwd: string, path: string, mode: 'hunk' | 'full', signal?: AbortSignal): Promise; /** * Stage and commit; optionally push afterwards. Returns the short hash. * When `exclude` names changed files, they are unstaged and left out of the * commit (they stay in the working tree); otherwise everything is staged * with `git add -A`. */ commit(cwd: string, message: string, opts?: { push?: boolean; exclude?: readonly string[]; tag?: string; signal?: AbortSignal; }): Promise<{ hash?: string; pushed: boolean; tag?: string; }>; /** Delete a local branch (force; the UI confirms first). Protected branches cannot be deleted. */ deleteBranch(cwd: string, name: string, signal?: AbortSignal): Promise; /** * Delete a remote branch (`origin/foo` → `git push origin --delete foo`). * Protected branches cannot be deleted. The local remote-tracking ref is * pruned afterwards so the branch list refreshes without waiting for a fetch. */ deleteRemoteBranch(cwd: string, name: string, signal?: AbortSignal): Promise; /** * Rename a branch (`git branch -m`). Renaming the current branch moves HEAD * with it; a remote upstream keeps its old name — pushing the new name and * deleting the old remote branch stays an explicit separate step. */ renameBranch(cwd: string, name: string, newName: string, signal?: AbortSignal): Promise; /** Tags newest-first, each with the short hash + subject of its commit. */ tags(cwd: string, signal?: AbortSignal): Promise; /** * Create a tag on HEAD, or on `ref` (any hash/branch the rev-parse accepts) * for back-tagger duty. With a message the tag is annotated, otherwise it is * a lightweight pointer. Refuses an already-taken name (git enforces this * too — the check just makes the API error readable). */ createTag(cwd: string, name: string, opts?: { message?: string; ref?: string; signal?: AbortSignal; }): Promise; /** Delete a local tag (`git tag -d`). Remote tags stay untouched. */ deleteTag(cwd: string, name: string, signal?: AbortSignal): Promise; /** Push one tag to `origin` — git never ships tags with a plain push. */ pushTag(cwd: string, name: string, signal?: AbortSignal): Promise; /** * Recent commits across all refs (`git log --date-order --all`), bounded by * `limit`. Parent hashes ride along so the client can lay out branch lanes * and render the fork/merge graph as SVG. */ graph(cwd: string, limit: number | undefined, signal?: AbortSignal): Promise; /** Push the current branch; a branch without upstream gets `-u origin `. */ push(cwd: string, signal?: AbortSignal): Promise; /** * Pull the current branch, fast-forward only: a diverged branch aborts with * git's own message instead of silently creating a merge commit, and a * branch without upstream fails with git's tracking-information error. */ pull(cwd: string, signal?: AbortSignal): Promise; /** Switch to an existing branch (git switch, falling back to checkout). */ checkout(cwd: string, branch: string, signal?: AbortSignal): Promise; /** * Create a branch from `base` (default: current HEAD) and switch to it. * When `pushRemote` is true, the new branch is also pushed to `origin` * with `-u`, creating it on the remote. */ createBranch(cwd: string, name: string, base: string | undefined, pushRemote: boolean, signal?: AbortSignal): Promise; } //# sourceMappingURL=git.d.ts.map