feat: add git stash management

Add a Stashes dialog with create, apply, pop, and drop actions
Include untracked files automatically when stashing
Show file counts for current changes and stash entries
This commit is contained in:
Bohdan Triapitsyn
2026-05-05 23:39:20 +03:00
parent c80c2b62a8
commit 93267927ff
19 changed files with 754 additions and 109 deletions
+57 -18
View File
@@ -21,6 +21,7 @@ import type {
GitPushResult,
GitPullResult,
GitPullOptions,
GitStashEntry,
GitLogOptions,
GitLogResponse,
GitCommitFilesResponse,
@@ -549,6 +550,58 @@ export async function gitFetch(
return response.json();
}
export async function listGitStashes(directory: string): Promise<{ stashes: GitStashEntry[] }> {
const response = await fetch(buildUrl(`${API_BASE}/stashes`, directory));
if (!response.ok) {
const error = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(error.error || 'Failed to list stashes');
}
return response.json();
}
export async function countGitStashFiles(directory: string, refs: string[]): Promise<{ counts: Record<string, number> }> {
const response = await fetch(buildUrl(`${API_BASE}/stashes/file-counts`, directory), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refs }),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(error.error || 'Failed to count stash files');
}
return response.json();
}
export async function stashGitChanges(directory: string, options: { message?: string } = {}): Promise<{ success: boolean; created: boolean; message: string; output: string }> {
const response = await fetch(buildUrl(`${API_BASE}/stash`, directory), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(options),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(error.error || 'Failed to stash changes');
}
return response.json();
}
const postStashRef = async (directory: string, path: string, options: { ref: string }): Promise<{ success: boolean; ref: string }> => {
const response = await fetch(buildUrl(`${API_BASE}/${path}`, directory), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(options),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(error.error || `Failed to ${path}`);
}
return response.json();
};
export const applyGitStash = (directory: string, options: { ref: string }) => postStashRef(directory, 'stash/apply', options);
export const popGitStash = (directory: string, options: { ref: string }) => postStashRef(directory, 'stash/pop', options);
export const dropGitStash = (directory: string, options: { ref: string }) => postStashRef(directory, 'stash/drop', options);
export async function checkoutBranch(directory: string, branch: string): Promise<{ success: boolean; branch: string }> {
const response = await fetch(buildUrl(`${API_BASE}/checkout`, directory), {
method: 'POST',
@@ -842,27 +895,13 @@ export async function stash(
directory: string,
options?: { message?: string; includeUntracked?: boolean }
): Promise<{ success: boolean }> {
const response = await fetch(buildUrl(`${API_BASE}/stash`, directory), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(options || {}),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(error.error || 'Failed to stash');
}
return response.json();
await stashGitChanges(directory, { message: options?.message });
return { success: true };
}
export async function stashPop(directory: string): Promise<{ success: boolean }> {
const response = await fetch(buildUrl(`${API_BASE}/stash/pop`, directory), {
method: 'POST',
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(error.error || 'Failed to pop stash');
}
return response.json();
await popGitStash(directory, { ref: 'stash@{0}' });
return { success: true };
}
export async function getConflictDetails(directory: string): Promise<MergeConflictDetails> {