Add a new Tauri command to produce a diff between the working file and a historical commit (get_file_restore_patch) and support a new apply action ("restore-lines") that validates and applies only text-line changes for a single regular file.
Behavior changes and constraints:
- Fetch a filtered reverse diff for a file in a commit so UI can display selectable lines from an older revision.
- Applying "restore-lines" verifies the target is a regular file, rejects binary/metadata patches, and ensures the patch only modifies the selected file.
- Restored lines are applied to the working tree without staging other changes; the index is preserved.
- The operation rejects stale patches or patches targeting the wrong file.
UI wiring:
- Compare dialog gets a "Restore lines…" action for applicable modified files and opens the line-patch dialog in restore mode.
- Line-patch dialog gains a restore mode (restoreCommit) with adjusted UI/rendering to pair removed/added lines, helper text, and dedicated "Restore selected" / "Restore hunk" actions.
- App integration handles fetching the restore patch, applying selected lines, and refreshing views.
Tests:
- Add tests covering correct behavior (preserve unstaged/staged changes and index) and guard cases (stale/wrong-file patches).
514 lines
11 KiB
TypeScript
514 lines
11 KiB
TypeScript
export type FileStatusKind =
|
|
| "modified"
|
|
| "added"
|
|
| "deleted"
|
|
| "renamed"
|
|
| "untracked"
|
|
| "conflicted"
|
|
| "unknown";
|
|
|
|
export type GitIgnoreKind = "file" | "extension" | "folder";
|
|
|
|
export type CommitAiProvider = "openai" | "anthropic" | "custom";
|
|
export type AppTheme = "system" | "light" | "dark";
|
|
export type AppAppearance = "modern" | "classic" | "custom";
|
|
export type AppLanguage = "en" | "de";
|
|
|
|
export type GitIntegrationProvider = "github" | "gitlab" | "gitlab-self-hosted" | "azure-devops" | "gitea";
|
|
|
|
export interface GitIntegrationConfig {
|
|
provider: GitIntegrationProvider;
|
|
enabled: boolean;
|
|
baseUrl: string;
|
|
username: string;
|
|
tokenStored: boolean;
|
|
}
|
|
|
|
export interface GitIntegrationSettings {
|
|
providers: Record<GitIntegrationProvider, GitIntegrationConfig>;
|
|
azureDevOpsOrganizations: AzureDevOpsOrganization[];
|
|
}
|
|
|
|
export interface AzureDevOpsOrganization {
|
|
id: string;
|
|
name: string;
|
|
enabled: boolean;
|
|
baseUrl: string;
|
|
username: string;
|
|
tokenStored: boolean;
|
|
}
|
|
|
|
export interface GitIntegrationSecretUpdate {
|
|
provider: GitIntegrationProvider;
|
|
accountId?: string;
|
|
token?: string;
|
|
removeToken?: boolean;
|
|
}
|
|
|
|
export interface GitIntegrationSource {
|
|
id: string;
|
|
provider: GitIntegrationProvider;
|
|
accountId?: string;
|
|
label: string;
|
|
baseUrl: string;
|
|
}
|
|
|
|
export interface GitIntegrationRepository {
|
|
id: string;
|
|
name: string;
|
|
fullName: string;
|
|
description: string;
|
|
cloneUrl: string;
|
|
sshUrl: string;
|
|
webUrl: string;
|
|
updatedAt: string;
|
|
private: boolean;
|
|
}
|
|
|
|
export type IntegrationReviewState = "open" | "draft" | "merged" | "closed";
|
|
export type IntegrationMergeMethod = "default" | "merge" | "squash" | "rebase" | "rebase-merge" | "fast-forward-only";
|
|
export interface IntegrationMergeOptions { methods: IntegrationMergeMethod[]; defaultMethod: IntegrationMergeMethod; }
|
|
|
|
export type IntegrationReviewAction = "merge" | "approve" | "close" | "reopen";
|
|
|
|
export interface IntegrationReviewComment {
|
|
id: string;
|
|
author: string;
|
|
body: string;
|
|
createdAt: string;
|
|
}
|
|
|
|
export interface IntegrationReviewRequest {
|
|
id: string;
|
|
number: number;
|
|
provider: GitIntegrationProvider;
|
|
repositoryId: string;
|
|
repositoryName: string;
|
|
title: string;
|
|
description: string;
|
|
author: string;
|
|
state: IntegrationReviewState;
|
|
sourceBranch: string;
|
|
targetBranch: string;
|
|
webUrl: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
collaborators: string[];
|
|
additions: number | null;
|
|
deletions: number | null;
|
|
changedFiles: number | null;
|
|
mergeStatus: "" | "mergeable" | "conflicts" | "checking" | "blocked";
|
|
comments: IntegrationReviewComment[];
|
|
}
|
|
|
|
export interface CustomThemeColors {
|
|
background: string;
|
|
surface: string;
|
|
accent: string;
|
|
text: string;
|
|
}
|
|
|
|
export type AiReviewRisk = "low" | "medium" | "high";
|
|
export type AiReviewSeverity = "critical" | "warning" | "info";
|
|
|
|
export interface AiReviewFinding {
|
|
severity: AiReviewSeverity;
|
|
title: string;
|
|
description: string;
|
|
file: string | null;
|
|
line: number | null;
|
|
suggestion: string;
|
|
}
|
|
|
|
export interface AiReviewResult {
|
|
summary: string;
|
|
risk: AiReviewRisk;
|
|
findings: AiReviewFinding[];
|
|
}
|
|
|
|
export interface AiCommitGroup {
|
|
message: string;
|
|
reason: string;
|
|
files: string[];
|
|
}
|
|
|
|
export interface AiCommitPlan {
|
|
summary: string;
|
|
groups: AiCommitGroup[];
|
|
}
|
|
|
|
export interface AiSettings {
|
|
provider: CommitAiProvider;
|
|
openaiModel: string;
|
|
anthropicModel: string;
|
|
customBaseUrl: string;
|
|
customModel: string;
|
|
}
|
|
|
|
export interface AnalyticsSettings {
|
|
enabled: boolean;
|
|
noticeSeen: boolean;
|
|
}
|
|
|
|
export interface ExternalToolCommand {
|
|
program: string;
|
|
args: string[];
|
|
}
|
|
|
|
export interface ExternalToolSetting extends ExternalToolCommand {
|
|
preset: string;
|
|
}
|
|
|
|
export type ExternalToolKind = "editor" | "diff" | "merge" | "terminal" | "fileManager";
|
|
export type ToolOpenMode = "gitty" | "external";
|
|
export type ExternalDiffScope = "head" | "staged" | "unstaged";
|
|
|
|
export interface ExternalToolsSettings {
|
|
editor: ExternalToolSetting;
|
|
diff: ExternalToolSetting;
|
|
merge: ExternalToolSetting;
|
|
terminal: ExternalToolSetting;
|
|
fileManager: ExternalToolSetting;
|
|
diffOpenMode: ToolOpenMode;
|
|
mergeOpenMode: ToolOpenMode;
|
|
}
|
|
|
|
export interface DetectedExternalTool {
|
|
id: string;
|
|
label: string;
|
|
program: string;
|
|
kinds: ExternalToolKind[];
|
|
}
|
|
|
|
export interface GitStatus {
|
|
repo_path: string;
|
|
current_branch: string | null;
|
|
upstream: string | null;
|
|
ahead: number;
|
|
behind: number;
|
|
files: GitFileStatus[];
|
|
clean: boolean;
|
|
rebase_in_progress: boolean;
|
|
cherry_pick_in_progress: boolean;
|
|
merge_in_progress: boolean;
|
|
}
|
|
|
|
export interface GitRemote { name: string; fetch_url: string; push_url: string; }
|
|
export type PullStrategy = "merge" | "rebase" | "ff-only";
|
|
export type MergeStrategy = "default" | "squash" | "ff-only" | "no-ff";
|
|
|
|
export interface CloneOptions {
|
|
branch: string | null;
|
|
blobless: boolean;
|
|
customFlags: string;
|
|
shallowDepth: number | null;
|
|
shallowSince: string | null;
|
|
sparse: boolean;
|
|
sparsePaths: string[];
|
|
}
|
|
|
|
export interface GitFileStatus {
|
|
path: string;
|
|
old_path: string | null;
|
|
staged: FileStatusKind | null;
|
|
unstaged: FileStatusKind | null;
|
|
}
|
|
|
|
export type PatchApplyAction = "restore-lines" | "stage" | "unstage" | "discard-unstaged" | "discard-staged";
|
|
|
|
export interface GitBranch {
|
|
name: string;
|
|
current: boolean;
|
|
remote: boolean;
|
|
upstream: string | null;
|
|
}
|
|
|
|
export interface GitWorktree {
|
|
path: string;
|
|
head: string | null;
|
|
short_head: string | null;
|
|
branch: string | null;
|
|
bare: boolean;
|
|
detached: boolean;
|
|
locked: boolean;
|
|
lock_reason: string | null;
|
|
prunable: boolean;
|
|
prune_reason: string | null;
|
|
missing: boolean;
|
|
is_main: boolean;
|
|
is_current: boolean;
|
|
clean: boolean;
|
|
changed_files: number;
|
|
}
|
|
|
|
export interface GitTag {
|
|
name: string;
|
|
hash: string;
|
|
short_hash: string;
|
|
message: string | null;
|
|
date: string;
|
|
annotated: boolean;
|
|
}
|
|
|
|
export interface GitStash {
|
|
selector: string;
|
|
index: number;
|
|
hash: string;
|
|
branch: string | null;
|
|
message: string;
|
|
date: string;
|
|
}
|
|
|
|
export interface GitCommit {
|
|
/** Set for the latest file-history entry when its diff against the working tree is empty. */
|
|
matches_working_tree?: boolean;
|
|
hash: string;
|
|
short_hash: string;
|
|
summary: string;
|
|
author_name: string;
|
|
author_email: string;
|
|
date: string;
|
|
refs: string[];
|
|
parents: string[];
|
|
files: GitCommitFile[];
|
|
has_note: boolean;
|
|
}
|
|
|
|
export interface BisectCommit {
|
|
hash: string;
|
|
short_hash: string;
|
|
summary: string;
|
|
author_name: string;
|
|
date: string;
|
|
}
|
|
|
|
export interface BisectState {
|
|
active: boolean;
|
|
finished: boolean;
|
|
current: BisectCommit | null;
|
|
culprit: BisectCommit | null;
|
|
remaining_revisions: number | null;
|
|
remaining_steps: number | null;
|
|
message: string;
|
|
}
|
|
|
|
export interface GitCommitFile {
|
|
path: string;
|
|
old_path: string | null;
|
|
status: FileStatusKind;
|
|
}
|
|
|
|
export interface GitRepositoryFile {
|
|
path: string;
|
|
tracked: boolean;
|
|
status: FileStatusKind | null;
|
|
}
|
|
|
|
export interface GitLfsPattern {
|
|
pattern: string;
|
|
source: string;
|
|
lockable: boolean;
|
|
tracked: boolean;
|
|
}
|
|
|
|
export interface GitLfsFile {
|
|
name: string;
|
|
size: number;
|
|
checkout: boolean;
|
|
downloaded: boolean;
|
|
oid_type: string;
|
|
oid: string;
|
|
version: string;
|
|
}
|
|
|
|
export interface GitLfsStatus {
|
|
available: boolean;
|
|
bundled: boolean;
|
|
version: string | null;
|
|
filters_configured: boolean;
|
|
hook_installed: boolean;
|
|
repository_uses_lfs: boolean;
|
|
patterns: GitLfsPattern[];
|
|
files: GitLfsFile[];
|
|
}
|
|
|
|
export interface RepositoryBundle {
|
|
status: GitStatus;
|
|
branches: GitBranch[];
|
|
tags: GitTag[];
|
|
stashes: GitStash[];
|
|
commits: GitCommit[];
|
|
files: GitRepositoryFile[];
|
|
warning: string | null;
|
|
}
|
|
|
|
export interface GitDiffFile {
|
|
path: string;
|
|
old_path: string | null;
|
|
status: FileStatusKind;
|
|
additions: number;
|
|
deletions: number;
|
|
}
|
|
|
|
export interface GitCommitComparison {
|
|
from_hash: string;
|
|
from_short: string;
|
|
to_hash: string;
|
|
to_short: string;
|
|
files: GitDiffFile[];
|
|
patch: string;
|
|
}
|
|
|
|
export interface GitSearchHit {
|
|
commit_hash: string;
|
|
short_hash: string;
|
|
summary: string;
|
|
author_name: string;
|
|
author_email: string;
|
|
date: string;
|
|
file: string;
|
|
old_file: string | null;
|
|
line_number: number | null;
|
|
line: string;
|
|
matches_added: number;
|
|
}
|
|
|
|
export type ExplorerNodeKind = "folder" | "file";
|
|
|
|
export interface ExplorerNode {
|
|
name: string;
|
|
path: string;
|
|
kind: ExplorerNodeKind;
|
|
status: FileStatusKind | null;
|
|
tracked: boolean;
|
|
depth: number;
|
|
children: ExplorerNode[];
|
|
}
|
|
|
|
export type ConflictChoice = "ours" | "theirs" | "both-ot" | "both-to";
|
|
|
|
export type ConflictPart =
|
|
| { kind: "text"; lines: string[] }
|
|
| { kind: "conflict"; index: number; oursLines: string[]; theirsLines: string[] };
|
|
|
|
export type PreparedResolution =
|
|
| { kind: "content"; content: string }
|
|
| { kind: "side"; side: "ours" | "theirs" };
|
|
|
|
export interface ConflictFile {
|
|
path: string;
|
|
content: string;
|
|
ours: string | null;
|
|
theirs: string | null;
|
|
base: string | null;
|
|
binary: boolean;
|
|
ours_size: number | null;
|
|
theirs_size: number | null;
|
|
}
|
|
|
|
export interface GitBlameLine {
|
|
line_number: number;
|
|
content: string;
|
|
commit_hash: string;
|
|
short_hash: string;
|
|
author_name: string;
|
|
author_email: string;
|
|
author_time: number;
|
|
summary: string;
|
|
is_uncommitted: boolean;
|
|
}
|
|
|
|
export interface GitBlameResult {
|
|
path: string;
|
|
lines: GitBlameLine[];
|
|
}
|
|
|
|
export type RebaseAction = "pick" | "reword" | "squash" | "fixup" | "drop";
|
|
|
|
export interface RebaseCommit {
|
|
hash: string;
|
|
short_hash: string;
|
|
summary: string;
|
|
author_name: string;
|
|
date: string;
|
|
}
|
|
|
|
export interface RebasePlanItem {
|
|
hash: string;
|
|
action: RebaseAction;
|
|
message: string | null;
|
|
}
|
|
|
|
export interface ReflogEntry {
|
|
hash: string;
|
|
short_hash: string;
|
|
selector: string;
|
|
action: string;
|
|
author_name: string;
|
|
date: string;
|
|
}
|
|
|
|
export interface StoredCredential {
|
|
username: string;
|
|
password: string;
|
|
mode?: "credentials" | "token";
|
|
}
|
|
|
|
export interface IntegrationIssue {
|
|
id: string;
|
|
number: number;
|
|
title: string;
|
|
description: string;
|
|
descriptionHtml: boolean;
|
|
repositoryName: string;
|
|
author: string;
|
|
state: string;
|
|
labels: string[];
|
|
assignees: string[];
|
|
webUrl: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface IntegrationIssuePage {
|
|
issues: IntegrationIssue[];
|
|
nextCursor: string | null;
|
|
}
|
|
|
|
export interface IntegrationBoardCard {
|
|
id: string;
|
|
title: string;
|
|
number: number;
|
|
webUrl: string;
|
|
repository: string;
|
|
description: string;
|
|
labels: string[];
|
|
assignees: string[];
|
|
lane: string;
|
|
}
|
|
|
|
export interface IntegrationBoard {
|
|
title: string;
|
|
webUrl: string;
|
|
notice: string;
|
|
columns: Array<{ id: string; title: string; limit: number; moveTarget?: Record<string, unknown> | null; cards: IntegrationBoardCard[] }>;
|
|
}
|
|
|
|
export interface IntegrationBoardReference { title: string; webUrl: string; scope: string }
|
|
|
|
export interface IssueComment { id: string; author: string; body: string; createdAt: string; bodyHtml: boolean }
|
|
|
|
export interface GitSubmodule {
|
|
name: string;
|
|
path: string;
|
|
owner_path: string;
|
|
relative_path: string;
|
|
full_path: string;
|
|
url: string;
|
|
branch: string | null;
|
|
recorded_commit: string;
|
|
local_commit: string | null;
|
|
dirty: boolean;
|
|
conflicted: boolean;
|
|
depth: number;
|
|
}
|