This update significantly expands Git functionality by implementing support for advanced workflows, including interactive rebasing and recovering lost commits via the reflog. New logic handles preparing the necessary environment files (todo lists and reword queues) required by Git's internal editors. The frontend components are also updated to expose these new capabilities to the user interface. - Implements full planning and execution flow for interactive rebase - Adds functionality to list and restore commits using the reflog history - Updates Rust backend commands to support advanced git operations
81 lines
4.7 KiB
Svelte
81 lines
4.7 KiB
Svelte
<script lang="ts">
|
|
import { GitBranch, History, LoaderCircle, Search, ShieldCheck, X } from "@lucide/svelte";
|
|
import type { ReflogEntry } from "../types";
|
|
|
|
interface Props {
|
|
entries: ReflogEntry[];
|
|
currentHash: string;
|
|
isLoading: boolean;
|
|
isBusy: boolean;
|
|
operation: string;
|
|
error: string;
|
|
onPreview: (entry: ReflogEntry) => void;
|
|
onRestore: (entry: ReflogEntry, branch: string) => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
let { entries = [], currentHash = "", isLoading = false, isBusy = false, operation = "", error = "", onPreview = () => {}, onRestore = () => {}, onClose = () => {} }: Props = $props();
|
|
let query = $state("");
|
|
let selectedHash = $state("");
|
|
let recoveryBranch = $state("");
|
|
let filteredEntries = $derived(entries.filter((entry) => `${entry.selector} ${entry.action} ${entry.short_hash} ${entry.author_name}`.toLowerCase().includes(query.trim().toLowerCase())));
|
|
let selected = $derived(entries.find((entry) => entry.hash === selectedHash) ?? filteredEntries[0] ?? null);
|
|
|
|
$effect(() => {
|
|
if (!selectedHash && entries.length > 0) select(entries[0]);
|
|
});
|
|
|
|
function select(entry: ReflogEntry) {
|
|
selectedHash = entry.hash;
|
|
recoveryBranch = `recovery/${entry.short_hash}`;
|
|
}
|
|
</script>
|
|
|
|
<div class="dialog-backdrop app-chrome-backdrop" role="presentation">
|
|
<div class="dialog reflog-dialog" role="dialog" aria-modal="true" aria-label="Reflog" tabindex="-1">
|
|
<header class="dialog-header">
|
|
<div><span class="eyebrow">Recovery history</span><h2 class="dialog-title">Reflog</h2></div>
|
|
<button class="dialog-close" type="button" onclick={onClose} disabled={isBusy} title="Close"><X size={18} aria-hidden="true" /></button>
|
|
</header>
|
|
<div class="reflog-body">
|
|
<aside class="reflog-list-pane">
|
|
<label class="reflog-search"><Search size={15} aria-hidden="true" /><input bind:value={query} placeholder="Search actions, hashes or authors" aria-label="Search reflog" /></label>
|
|
{#if isLoading}
|
|
<div class="blank-state"><LoaderCircle class="spin" size={18} aria-hidden="true" /> Loading reflog…</div>
|
|
{:else if filteredEntries.length === 0}
|
|
<div class="blank-state">No reflog entries match this search.</div>
|
|
{:else}
|
|
<div class="reflog-list" role="listbox" aria-label="Reflog entries">
|
|
{#each filteredEntries as entry (`${entry.selector}:${entry.hash}`)}
|
|
<button class:active={selected?.selector === entry.selector} type="button" role="option" aria-selected={selected?.selector === entry.selector} onclick={() => select(entry)}>
|
|
<span class="reflog-row-top"><code>{entry.selector}</code><span>{new Date(entry.date).toLocaleString()}</span></span>
|
|
<strong>{entry.action}</strong>
|
|
<span class="reflog-row-bottom"><code>{entry.short_hash}</code><span>{entry.author_name}</span></span>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</aside>
|
|
|
|
<section class="reflog-detail">
|
|
{#if error}<div class="rebase-warning error">{error}</div>{/if}
|
|
{#if selected}
|
|
<div class="reflog-detail-head"><History size={20} aria-hidden="true" /><div><span class="eyebrow">{selected.selector}</span><h3>{selected.action}</h3></div></div>
|
|
<dl><div><dt>Commit</dt><dd><code>{selected.hash}</code></dd></div><div><dt>Author</dt><dd>{selected.author_name}</dd></div><div><dt>Date</dt><dd>{new Date(selected.date).toLocaleString()}</dd></div></dl>
|
|
<button class="btn-secondary reflog-preview" type="button" onclick={() => onPreview(selected)} disabled={isBusy || selected.hash === currentHash}><History size={15} aria-hidden="true" /> Preview changes to current HEAD</button>
|
|
<div class="reflog-recovery-card">
|
|
<div class="reflog-recovery-title"><ShieldCheck size={18} aria-hidden="true" /><div><strong>Safe recovery</strong><span>Create a new branch here. The current branch is not reset or deleted.</span></div></div>
|
|
<label><span>Recovery branch</span><div><GitBranch size={15} aria-hidden="true" /><input bind:value={recoveryBranch} disabled={isBusy} spellcheck="false" /></div></label>
|
|
<button class="btn-primary" type="button" onclick={() => onRestore(selected, recoveryBranch.trim())} disabled={isBusy || !recoveryBranch.trim()}>
|
|
{#if operation === "Restoring reflog entry"}<LoaderCircle class="spin" size={16} aria-hidden="true" />{:else}<ShieldCheck size={16} aria-hidden="true" />{/if}
|
|
Create and checkout recovery branch
|
|
</button>
|
|
</div>
|
|
{:else}
|
|
<div class="blank-state">Select a reflog entry to inspect or recover it.</div>
|
|
{/if}
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|