feat(git stash): add stash listing and push/apply/pop/drop UI
This change introduces Git stash support end-to-end, including a new backend command to list stashes and operations to push, apply, pop, and drop them. The frontend now fetches stashes as part of the repository bundle and provides a dedicated panel to manage shelved changes. - Add GitStash model and Tauri commands for stash operations - Implement StashPanel component and wire it into the app - Adjust sidebar layout and add stash-specific styling
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
import RenameBranchDialog from "./lib/components/RenameBranchDialog.svelte";
|
||||
import RepoLoadingOverlay from "./lib/components/RepoLoadingOverlay.svelte";
|
||||
import ResolveDialog from "./lib/components/ResolveDialog.svelte";
|
||||
import StashPanel from "./lib/components/StashPanel.svelte";
|
||||
import StatusPanel from "./lib/components/StatusPanel.svelte";
|
||||
import UpdateToast from "./lib/components/UpdateToast.svelte";
|
||||
|
||||
@@ -42,6 +43,7 @@
|
||||
fetchRemote,
|
||||
getStatus,
|
||||
listBranches,
|
||||
listStashes,
|
||||
listCommits,
|
||||
listFileHistory,
|
||||
listRepositoryFiles,
|
||||
@@ -66,6 +68,10 @@
|
||||
searchCodeIntroductions,
|
||||
setSyncBadge,
|
||||
stageFiles,
|
||||
stashApply,
|
||||
stashDrop,
|
||||
stashPop,
|
||||
stashPush,
|
||||
unstageFiles,
|
||||
} from "./lib/git";
|
||||
|
||||
@@ -83,6 +89,7 @@
|
||||
GitFileStatus,
|
||||
GitRepositoryFile,
|
||||
GitSearchHit,
|
||||
GitStash,
|
||||
GitStatus,
|
||||
LocalModelOption,
|
||||
PatchApplyAction,
|
||||
@@ -135,6 +142,7 @@
|
||||
let repoSearch = "";
|
||||
let status: GitStatus | null = null;
|
||||
let branches: GitBranchInfo[] = [];
|
||||
let stashes: GitStash[] = [];
|
||||
let commits: GitCommit[] = [];
|
||||
let repoFiles: GitRepositoryFile[] = [];
|
||||
let selectedExplorerPath = "";
|
||||
@@ -298,6 +306,7 @@
|
||||
const bundle = await openRepositoryBundle(activeRepoPath, 100);
|
||||
const previousHeadHash = lastFileHistoryHeadHash;
|
||||
await refreshBranchList(activeRepoPath, bundle.branches);
|
||||
await refreshStashes(activeRepoPath, bundle.stashes);
|
||||
await refreshCommitHistory(activeRepoPath, bundle.commits);
|
||||
await refreshExplorerFiles(activeRepoPath, bundle.files);
|
||||
// File history reflects `git log`, which only changes when HEAD actually moves
|
||||
@@ -744,6 +753,7 @@
|
||||
void setSyncBadge(0, 0, 0).catch(() => {});
|
||||
}
|
||||
branches = [];
|
||||
stashes = [];
|
||||
commits = [];
|
||||
lastFileHistoryHeadHash = "";
|
||||
repoFiles = [];
|
||||
@@ -835,6 +845,10 @@
|
||||
branches = prefetched ?? (await listBranches(path));
|
||||
}
|
||||
|
||||
async function refreshStashes(path = activeRepoPath, prefetched?: GitStash[]) {
|
||||
stashes = prefetched ?? (await listStashes(path));
|
||||
}
|
||||
|
||||
async function refreshCommitHistory(path = activeRepoPath, prefetched?: GitCommit[]) {
|
||||
commits = prefetched ?? (await listCommits(path, 100));
|
||||
lastFileHistoryHeadHash = commits[0]?.hash ?? "";
|
||||
@@ -929,6 +943,7 @@
|
||||
if (globalSearchBusy) void cancelGlobalSearch();
|
||||
activeView = "repository";
|
||||
await refreshBranchList(activeRepoPath, bundle.branches);
|
||||
await refreshStashes(activeRepoPath, bundle.stashes);
|
||||
await refreshCommitHistory(activeRepoPath, bundle.commits);
|
||||
await refreshExplorerFiles(activeRepoPath, bundle.files);
|
||||
lastRepoSwitchAt = Date.now();
|
||||
@@ -1006,6 +1021,7 @@
|
||||
await runOperation("Refreshing", async () => {
|
||||
applyStatus(await getStatus(activeRepoPath));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshStashes(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
@@ -1301,6 +1317,47 @@
|
||||
await startRemoteAction("push");
|
||||
}
|
||||
|
||||
async function saveStash(message: string, includeUntracked: boolean) {
|
||||
if (!activeRepoPath || changedFiles.length === 0) return;
|
||||
await runOperation("Stashing changes", async () => {
|
||||
applyStatus(await stashPush(activeRepoPath, message, includeUntracked));
|
||||
await refreshStashes(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
});
|
||||
}
|
||||
|
||||
async function applyStashEntry(stash: GitStash) {
|
||||
if (!activeRepoPath) return;
|
||||
await runOperation(`Applying ${stash.selector}`, async () => {
|
||||
applyStatus(await stashApply(activeRepoPath, stash.selector));
|
||||
await refreshStashes(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
});
|
||||
}
|
||||
|
||||
async function popStashEntry(stash: GitStash) {
|
||||
if (!activeRepoPath) return;
|
||||
await runOperation(`Popping ${stash.selector}`, async () => {
|
||||
applyStatus(await stashPop(activeRepoPath, stash.selector));
|
||||
await refreshStashes(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
});
|
||||
}
|
||||
|
||||
async function dropStashEntry(stash: GitStash) {
|
||||
if (!activeRepoPath) return;
|
||||
const confirmed = window.confirm(`Delete ${stash.selector}?\n\n"${stash.message || stash.selector}"`);
|
||||
if (!confirmed) return;
|
||||
|
||||
await runOperation(`Dropping ${stash.selector}`, async () => {
|
||||
applyStatus(await stashDrop(activeRepoPath, stash.selector));
|
||||
await refreshStashes(activeRepoPath);
|
||||
});
|
||||
}
|
||||
|
||||
// ── File staging / restore ─────────────────────────────────────────────────
|
||||
|
||||
async function stageFile(file: GitFileStatus) {
|
||||
@@ -2020,6 +2077,16 @@
|
||||
onRenameBranch={renameLocalBranch}
|
||||
onDeleteBranch={deleteLocalBranch}
|
||||
/>
|
||||
<StashPanel
|
||||
{stashes}
|
||||
changedCount={changedFiles.length}
|
||||
{hasRepository}
|
||||
{isBusy}
|
||||
onPush={saveStash}
|
||||
onApply={applyStashEntry}
|
||||
onPop={popStashEntry}
|
||||
onDrop={dropStashEntry}
|
||||
/>
|
||||
<ExplorerPanel
|
||||
{repoFiles}
|
||||
{expandedExplorerPaths}
|
||||
|
||||
Reference in New Issue
Block a user