This change introduces new Tauri commands to amend the last commit with an optional message, fetch the last commit message, and undo the most recent commit safely. The UI now offers an amend toggle and an undo button only when the last commit hasn’t been pushed upstream, reducing the risk of rewriting shared history. - Add amend/undo/last-message git commands in Rust - Wire new operations into the Svelte commit panel UI - Add styling and state handling for amend mode
147 lines
4.7 KiB
Svelte
147 lines
4.7 KiB
Svelte
<script lang="ts">
|
|
import { Check, LoaderCircle, RotateCcw, Settings, Sparkles } from "@lucide/svelte";
|
|
import type { CommitAiPhase, CommitAiProvider } from "../types";
|
|
|
|
interface Props {
|
|
commitMessage: string;
|
|
canCommit: boolean;
|
|
commitBlockReason: string;
|
|
hasRepository: boolean;
|
|
isBusy: boolean;
|
|
operation: string;
|
|
stagedCount: number;
|
|
commitAiProvider: CommitAiProvider;
|
|
commitAiPhase: CommitAiPhase;
|
|
commitAiGenerating: boolean;
|
|
canAmend: boolean;
|
|
amendMode: boolean;
|
|
onCommit: () => void;
|
|
onCommitMessageChange: (msg: string) => void;
|
|
onGenerateCommitMessage: () => void;
|
|
onOpenAiSettings: () => void;
|
|
onToggleAmend: (checked: boolean) => void;
|
|
onUndoLastCommit: () => void;
|
|
}
|
|
|
|
let {
|
|
commitMessage = "",
|
|
canCommit = false,
|
|
commitBlockReason = "",
|
|
hasRepository = false,
|
|
isBusy = false,
|
|
operation = "",
|
|
stagedCount = 0,
|
|
commitAiProvider = "local",
|
|
commitAiPhase = "idle",
|
|
commitAiGenerating = false,
|
|
canAmend = false,
|
|
amendMode = false,
|
|
onCommit = () => {},
|
|
onCommitMessageChange = () => {},
|
|
onGenerateCommitMessage = () => {},
|
|
onOpenAiSettings = () => {},
|
|
onToggleAmend = () => {},
|
|
onUndoLastCommit = () => {},
|
|
}: Props = $props();
|
|
|
|
function handleSubmit(event: SubmitEvent) {
|
|
event.preventDefault();
|
|
onCommit();
|
|
}
|
|
|
|
function aiButtonTitle(provider: CommitAiProvider, phase: CommitAiPhase, staged: number): string {
|
|
if (staged === 0) return "Stage changes first";
|
|
if (provider === "local" && phase === "loading") return "AI model is downloading/loading — this happens once";
|
|
if (provider === "local" && phase === "error") return "AI model failed to load — check AI settings";
|
|
return "Generate commit message with AI from the staged diff";
|
|
}
|
|
|
|
let localModelLoading = $derived(commitAiProvider === "local" && commitAiPhase === "loading");
|
|
let canGenerate = $derived(
|
|
hasRepository &&
|
|
!isBusy &&
|
|
!commitAiGenerating &&
|
|
stagedCount > 0 &&
|
|
(commitAiProvider !== "local" || commitAiPhase === "ready"),
|
|
);
|
|
</script>
|
|
|
|
<section class="panel commit-panel" aria-label="Commit">
|
|
<div class="section-head">
|
|
<div>
|
|
<span class="eyebrow">Commit</span>
|
|
<h2 class="mt-0.5 text-ink text-base font-bold leading-tight">Message</h2>
|
|
</div>
|
|
<span class="pill pill-count">{stagedCount} staged</span>
|
|
</div>
|
|
|
|
<form class="commit-form" onsubmit={handleSubmit}>
|
|
<textarea
|
|
value={commitMessage}
|
|
oninput={(e) => onCommitMessageChange((e.target as HTMLTextAreaElement).value)}
|
|
placeholder="Commit message..."
|
|
disabled={!hasRepository || isBusy}
|
|
></textarea>
|
|
{#if commitBlockReason}
|
|
<p class="commit-block-reason">{commitBlockReason}</p>
|
|
{/if}
|
|
{#if canAmend}
|
|
<div class="commit-amend-row">
|
|
<label class="commit-amend-toggle">
|
|
<input
|
|
type="checkbox"
|
|
checked={amendMode}
|
|
disabled={isBusy}
|
|
onchange={(e) => onToggleAmend((e.target as HTMLInputElement).checked)}
|
|
/>
|
|
Amend last commit
|
|
</label>
|
|
<button
|
|
class="btn-secondary commit-undo-button"
|
|
type="button"
|
|
onclick={onUndoLastCommit}
|
|
disabled={isBusy}
|
|
title="Undo the last commit — its changes come back as uncommitted changes (not pushed yet, so safe)"
|
|
>
|
|
<RotateCcw size={13} aria-hidden="true" />
|
|
Undo last commit
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
<div class="commit-actions-row flex-shrink-0">
|
|
<button class="btn-primary flex-1" type="submit" disabled={!canCommit} title={commitBlockReason || (amendMode ? "Amend the last commit" : "Commit staged changes")}>
|
|
{#if operation === "Committing" || operation === "Amending"}
|
|
<LoaderCircle class="spin" size={16} aria-hidden="true" />
|
|
{:else}
|
|
<Check size={16} aria-hidden="true" />
|
|
{/if}
|
|
{amendMode ? "Amend" : "Commit"}
|
|
</button>
|
|
<button
|
|
class="btn-secondary commit-ai-button"
|
|
type="button"
|
|
onclick={onGenerateCommitMessage}
|
|
disabled={!canGenerate}
|
|
title={aiButtonTitle(commitAiProvider, commitAiPhase, stagedCount)}
|
|
>
|
|
{#if commitAiGenerating || localModelLoading}
|
|
<LoaderCircle class="spin" size={16} aria-hidden="true" />
|
|
{:else}
|
|
<Sparkles size={16} aria-hidden="true" />
|
|
{/if}
|
|
AI
|
|
</button>
|
|
<button
|
|
class="btn-secondary commit-ai-settings-button"
|
|
type="button"
|
|
onclick={onOpenAiSettings}
|
|
disabled={isBusy}
|
|
title="AI settings"
|
|
aria-label="AI settings"
|
|
>
|
|
<Settings size={16} aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</section>
|