Implement interactive hunk staging and discarding

Introduce a new dialog that allows users to view file changes as individual hunks and selectively stage, unstage, or discard them. This provides more granular control over modifications, similar to `git add -p`.

Enhance branch panel usability by allowing double-click to checkout a branch.
This commit is contained in:
Christoph Brandau
2026-07-01 14:47:36 +02:00
parent 0bbd5ce1e8
commit 5752243e6e
9 changed files with 636 additions and 11 deletions
+15 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { Check, RotateCcw, Undo2 } from "@lucide/svelte";
import { Check, FileDiff, RotateCcw, Undo2 } from "@lucide/svelte";
import type { FileStatusKind, GitFileStatus, GitStatus } from "../types";
interface Props {
@@ -12,6 +12,7 @@
onStage: (file: GitFileStatus) => void;
onUnstage: (file: GitFileStatus) => void;
onDiscard: (file: GitFileStatus, staged: boolean) => void;
onPatch: (file: GitFileStatus, staged: boolean) => void;
onStageAll: () => void;
onUnstageAll: () => void;
}
@@ -26,6 +27,7 @@
onStage = () => {},
onUnstage = () => {},
onDiscard = () => {},
onPatch = () => {},
onStageAll = () => {},
onUnstageAll = () => {},
}: Props = $props();
@@ -46,6 +48,10 @@
return file.old_path ? `${baseName(file.old_path)} -> ${baseName(file.path)}` : baseName(file.path);
}
function canPatch(kind: FileStatusKind | null): boolean {
return kind === "modified";
}
let hasUnstaged = $derived(changedFiles.some((f) => f.unstaged !== null));
let hasStaged = $derived(changedFiles.some((f) => f.staged !== null));
</script>
@@ -113,6 +119,10 @@
<Undo2 size={14} aria-hidden="true" />
Unstage
</button>
<button class="btn-sm" type="button" onclick={() => onPatch(file, true)} disabled={isBusy || !canPatch(file.staged)} title="Stage, unstage, or discard selected lines">
<FileDiff size={14} aria-hidden="true" />
Lines
</button>
<button class="btn-sm" type="button" onclick={() => onDiscard(file, true)} disabled={isBusy} title="Discard staged changes">
<RotateCcw size={14} aria-hidden="true" />
Discard
@@ -134,6 +144,10 @@
<Check size={14} aria-hidden="true" />
Stage
</button>
<button class="btn-sm" type="button" onclick={() => onPatch(file, false)} disabled={isBusy || !canPatch(file.unstaged)} title="Stage or discard selected lines">
<FileDiff size={14} aria-hidden="true" />
Lines
</button>
<button class="btn-sm" type="button" onclick={() => onDiscard(file, false)} disabled={isBusy} title="Discard unstaged changes">
<RotateCcw size={14} aria-hidden="true" />
Discard