feat(git): add rebase workflow with UI state handling #14
@@ -47,6 +47,7 @@ pub struct GitStatus {
|
||||
pub behind: u32,
|
||||
pub files: Vec<GitFileStatus>,
|
||||
pub clean: bool,
|
||||
pub rebase_in_progress: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
@@ -1119,6 +1120,72 @@ pub fn merge_branch(path: String, branch: String) -> Result<GitStatus, String> {
|
||||
Err(format!("Merge failed: {details}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn rebase_branch(path: String, branch: String) -> Result<GitStatus, String> {
|
||||
let repo = resolve_repo(&path)?;
|
||||
let branch = branch.trim();
|
||||
if branch.is_empty() {
|
||||
return Err("Branch name must not be empty.".to_string());
|
||||
}
|
||||
|
||||
let output = git_command()
|
||||
.arg("-C")
|
||||
.arg(&repo)
|
||||
.args(["rebase", branch])
|
||||
.output()
|
||||
.map_err(|err| format!("Could not start Git. Is Git installed? {err}"))?;
|
||||
|
||||
rebase_status_or_error(&repo, output, "Rebase failed", true)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn rebase_continue(path: String) -> Result<GitStatus, String> {
|
||||
let repo = resolve_repo(&path)?;
|
||||
if !rebase_in_progress(&repo) {
|
||||
return Err("No rebase is currently in progress.".to_string());
|
||||
}
|
||||
|
||||
let output = git_command()
|
||||
.arg("-C")
|
||||
.arg(&repo)
|
||||
.args(["rebase", "--continue"])
|
||||
.env("GIT_EDITOR", "true")
|
||||
.output()
|
||||
.map_err(|err| format!("Could not start Git. Is Git installed? {err}"))?;
|
||||
|
||||
rebase_status_or_error(&repo, output, "Rebase continue failed", false)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn rebase_abort(path: String) -> Result<GitStatus, String> {
|
||||
let repo = resolve_repo(&path)?;
|
||||
if !rebase_in_progress(&repo) {
|
||||
return Err("No rebase is currently in progress.".to_string());
|
||||
}
|
||||
|
||||
run_git(&repo, ["rebase", "--abort"])?;
|
||||
status_for_repo(&repo)
|
||||
}
|
||||
|
||||
fn rebase_status_or_error(
|
||||
repo: &Path,
|
||||
output: Output,
|
||||
context: &str,
|
||||
ok_if_rebase_in_progress: bool,
|
||||
) -> Result<GitStatus, String> {
|
||||
if output.status.success() {
|
||||
return status_for_repo(repo);
|
||||
}
|
||||
|
||||
let status = status_for_repo(repo)?;
|
||||
if has_unresolved_conflicts(&status) || (ok_if_rebase_in_progress && status.rebase_in_progress)
|
||||
{
|
||||
return Ok(status);
|
||||
}
|
||||
|
||||
Err(format!("{context}: {}", command_output_details(&output)))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn list_commits(path: String, limit: Option<u32>) -> Result<Vec<GitCommit>, String> {
|
||||
let repo = resolve_repo(&path)?;
|
||||
@@ -1953,9 +2020,30 @@ fn status_for_repo(repo: &Path) -> Result<GitStatus, String> {
|
||||
behind: branch.behind,
|
||||
clean: files.is_empty(),
|
||||
files,
|
||||
rebase_in_progress: rebase_in_progress(repo),
|
||||
})
|
||||
}
|
||||
|
||||
fn rebase_in_progress(repo: &Path) -> bool {
|
||||
git_path_exists(repo, "rebase-merge") || git_path_exists(repo, "rebase-apply")
|
||||
}
|
||||
|
||||
fn git_path_exists(repo: &Path, name: &str) -> bool {
|
||||
let Ok(output) = run_git(repo, ["rev-parse", "--git-path", name]) else {
|
||||
return false;
|
||||
};
|
||||
let value = String::from_utf8_lossy(&output).trim().to_string();
|
||||
if value.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let path = PathBuf::from(value);
|
||||
if path.is_absolute() {
|
||||
path.exists()
|
||||
} else {
|
||||
repo.join(path).exists()
|
||||
}
|
||||
}
|
||||
|
||||
// `git status` only auto-detects renames between HEAD and the index (staged changes).
|
||||
// A file renamed on disk but not yet `git add`ed shows up as a plain delete + untracked
|
||||
// pair instead. We detect that case ourselves by comparing content hashes: if an unstaged
|
||||
|
||||
@@ -11,10 +11,10 @@ use git::{
|
||||
cred_delete, cred_load, cred_save, delete_branch, diff_file_against_working_tree, fetch,
|
||||
get_file_patch, get_remote_url, get_status, list_branches, list_commits, list_file_history,
|
||||
list_repository_files, list_stashes, merge_branch, open_repo_in_explorer, open_repository,
|
||||
open_repository_bundle, open_repository_file, pull, push, read_conflict, rename_branch,
|
||||
resolve_conflict, resolve_conflict_side, restore_file_from_commit, restore_files,
|
||||
restore_to_commit, search_code_introductions, stage_files, stash_apply, stash_drop, stash_pop,
|
||||
stash_push, unstage_files,
|
||||
open_repository_bundle, open_repository_file, pull, push, read_conflict, rebase_abort,
|
||||
rebase_branch, rebase_continue, rename_branch, resolve_conflict, resolve_conflict_side,
|
||||
restore_file_from_commit, restore_files, restore_to_commit, search_code_introductions,
|
||||
stage_files, stash_apply, stash_drop, stash_pop, stash_push, unstage_files,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
@@ -55,6 +55,9 @@ fn main() {
|
||||
restore_to_commit,
|
||||
restore_file_from_commit,
|
||||
merge_branch,
|
||||
rebase_branch,
|
||||
rebase_continue,
|
||||
rebase_abort,
|
||||
list_repository_files,
|
||||
open_repository_bundle,
|
||||
list_file_history,
|
||||
|
||||
+76
-6
@@ -54,6 +54,9 @@
|
||||
pull,
|
||||
push,
|
||||
renameBranch,
|
||||
rebaseAbort,
|
||||
rebaseBranch,
|
||||
rebaseContinue,
|
||||
getRemoteUrl,
|
||||
credLoad,
|
||||
credSave,
|
||||
@@ -236,9 +239,12 @@
|
||||
$: unstagedCount = status?.files.filter((f) => f.unstaged !== null).length ?? 0;
|
||||
$: conflictedFiles = changedFiles.filter((f) => f.staged === "conflicted" || f.unstaged === "conflicted");
|
||||
$: hasConflicts = conflictedFiles.length > 0;
|
||||
$: canCommit = hasRepository && stagedCount > 0 && commitMessage.trim().length > 0 && !hasConflicts && !isBusy;
|
||||
$: commitBlockReason = hasConflicts
|
||||
? `${conflictedFiles.length} ${conflictedFiles.length === 1 ? "merge conflict must" : "merge conflicts must"} be resolved before committing.`
|
||||
$: rebaseInProgress = status?.rebase_in_progress ?? false;
|
||||
$: canCommit = hasRepository && stagedCount > 0 && commitMessage.trim().length > 0 && !hasConflicts && !rebaseInProgress && !isBusy;
|
||||
$: commitBlockReason = rebaseInProgress
|
||||
? "A rebase is in progress. Resolve conflicts and use Rebase continue or abort the rebase."
|
||||
: hasConflicts
|
||||
? `${conflictedFiles.length} ${conflictedFiles.length === 1 ? "conflict must" : "conflicts must"} be resolved before committing.`
|
||||
: "";
|
||||
$: canCompare = hasRepository && compareFrom.length > 0 && compareTo.length > 0 && compareFrom !== compareTo && !isBusy;
|
||||
$: localBranches = branches.filter((b) => !b.remote);
|
||||
@@ -1120,6 +1126,46 @@
|
||||
});
|
||||
}
|
||||
|
||||
async function rebaseOnto(branch: GitBranchInfo) {
|
||||
if (!activeRepoPath || branch.current || rebaseInProgress) return;
|
||||
await runOperation(`Rebasing onto ${branch.name}`, async () => {
|
||||
applyStatus(await rebaseBranch(activeRepoPath, branch.name));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
});
|
||||
}
|
||||
|
||||
async function continueRebase() {
|
||||
if (!activeRepoPath || !rebaseInProgress || hasConflicts) return;
|
||||
await runOperation("Continuing rebase", async () => {
|
||||
applyStatus(await rebaseContinue(activeRepoPath));
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
});
|
||||
}
|
||||
|
||||
async function abortRebase() {
|
||||
if (!activeRepoPath || !rebaseInProgress) return;
|
||||
const confirmed = window.confirm("Abort the current rebase and return to the previous state?");
|
||||
if (!confirmed) return;
|
||||
|
||||
await runOperation("Aborting rebase", async () => {
|
||||
applyStatus(await rebaseAbort(activeRepoPath));
|
||||
preparedResolutions = {};
|
||||
resolveDialogOpen = false;
|
||||
conflict = null;
|
||||
conflictTarget = "";
|
||||
await refreshBranchList(activeRepoPath);
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
});
|
||||
}
|
||||
|
||||
// Resolve the keychain key (host/org) for the active repo's remote.
|
||||
async function currentCredKey(): Promise<string | null> {
|
||||
if (!activeRepoPath) return null;
|
||||
@@ -1519,7 +1565,11 @@
|
||||
const message = commitMessage.trim();
|
||||
if (!message || !activeRepoPath) return;
|
||||
if (hasConflicts) {
|
||||
errorMessage = "Resolve all merge conflicts before committing.";
|
||||
errorMessage = "Resolve all conflicts before committing.";
|
||||
return;
|
||||
}
|
||||
if (rebaseInProgress) {
|
||||
errorMessage = "A rebase is in progress. Use Rebase continue or abort the rebase.";
|
||||
return;
|
||||
}
|
||||
await runOperation("Committing", async () => {
|
||||
@@ -1939,14 +1989,33 @@
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if workspaceActive && hasConflicts}
|
||||
{#if workspaceActive && hasConflicts && !rebaseInProgress}
|
||||
<section class="notice conflict" role="alert">
|
||||
<GitMerge size={17} aria-hidden="true" />
|
||||
<span>{conflictedFiles.length} {conflictedFiles.length === 1 ? "file has" : "files have"} merge conflicts.</span>
|
||||
<span>{conflictedFiles.length} {conflictedFiles.length === 1 ? "file has" : "files have"} conflicts.</span>
|
||||
<button type="button" onclick={openResolveDialog} disabled={isBusy}>Resolve conflicts</button>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if workspaceActive && rebaseInProgress}
|
||||
<section class="notice rebase" role="status">
|
||||
<GitBranch size={17} aria-hidden="true" />
|
||||
<span>
|
||||
Rebase in progress.
|
||||
{#if hasConflicts}
|
||||
Resolve conflicts, then continue.
|
||||
{:else}
|
||||
Continue when the index is ready, or abort to return to the previous state.
|
||||
{/if}
|
||||
</span>
|
||||
{#if hasConflicts}
|
||||
<button type="button" onclick={openResolveDialog} disabled={isBusy}>Resolve conflicts</button>
|
||||
{/if}
|
||||
<button type="button" onclick={continueRebase} disabled={isBusy || hasConflicts}>Continue</button>
|
||||
<button type="button" onclick={abortRebase} disabled={isBusy}>Abort</button>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if activeView === "management"}
|
||||
<section class="repo-management" aria-label="Repository Management">
|
||||
<div class="repo-management-head">
|
||||
@@ -2073,6 +2142,7 @@
|
||||
{isBusy}
|
||||
onCheckout={checkout}
|
||||
onMerge={merge}
|
||||
onRebase={rebaseOnto}
|
||||
onCreateBranch={createNewBranch}
|
||||
onRenameBranch={renameLocalBranch}
|
||||
onDeleteBranch={deleteLocalBranch}
|
||||
|
||||
+54
-1
@@ -862,6 +862,8 @@
|
||||
.notice.error { border-color: rgba(232,96,90,0.3); color: #f09090; background: rgba(232,96,90,0.08); }
|
||||
.notice.busy { border-color: rgba(90,140,248,0.28); color: #8ab0f8; background: rgba(90,140,248,0.07); }
|
||||
.notice.conflict { border-color: rgba(224,160,64,0.3); color: #e8b060; background: rgba(224,160,64,0.07); }
|
||||
.notice.rebase { flex-wrap: wrap; border-color: rgba(186,130,255,0.3); color: #c9a8ff; background: rgba(186,130,255,0.075); }
|
||||
.notice.rebase span { min-width: 0; flex: 1 1 auto; }
|
||||
.notice.conflict button {
|
||||
margin-left: auto;
|
||||
min-height: 26px;
|
||||
@@ -877,6 +879,20 @@
|
||||
border-color: rgba(224,160,64,0.45);
|
||||
color: #f0c070;
|
||||
}
|
||||
.notice.rebase button {
|
||||
min-height: 26px;
|
||||
padding: 0 10px;
|
||||
border-color: rgba(186,130,255,0.28);
|
||||
color: #d5bdff;
|
||||
background: rgba(186,130,255,0.1);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.notice.rebase button:hover:not(:disabled) {
|
||||
background: rgba(186,130,255,0.18);
|
||||
border-color: rgba(186,130,255,0.45);
|
||||
color: #eadfff;
|
||||
}
|
||||
|
||||
/* --- Update toast --- */
|
||||
|
||||
@@ -1110,6 +1126,10 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.left-sidebar:has(.stash-panel.collapsed) {
|
||||
grid-template-rows: minmax(170px, 0.85fr) auto minmax(220px, 1.15fr);
|
||||
}
|
||||
|
||||
/* --- Main panel --- */
|
||||
|
||||
.main-panel {
|
||||
@@ -1276,6 +1296,36 @@
|
||||
grid-template-rows: auto auto minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.stash-panel.collapsed {
|
||||
grid-template-rows: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.stash-head-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.stash-toggle {
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
width: 26px;
|
||||
min-width: 26px;
|
||||
min-height: 26px;
|
||||
padding: 0;
|
||||
border-color: rgba(94,110,156,0.18);
|
||||
border-radius: 7px;
|
||||
color: var(--color-ink-dim);
|
||||
background: rgba(255,255,255,0.035);
|
||||
}
|
||||
|
||||
.stash-toggle:hover:not(:disabled) {
|
||||
border-color: rgba(65,209,255,0.28);
|
||||
color: var(--color-ink);
|
||||
background: rgba(65,209,255,0.08);
|
||||
}
|
||||
|
||||
.stash-create {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
@@ -1585,7 +1635,8 @@
|
||||
.branch-info strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; color: var(--color-ink); }
|
||||
.branch-info span { display: block; margin-top: 2px; color: var(--color-ink-dim); font-size: 11px; }
|
||||
|
||||
.branch-actions { display: flex; align-items: center; justify-content: flex-end; gap: 5px; }
|
||||
.branch-actions { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 5px; }
|
||||
.branch-actions .btn-sm { min-height: 24px; padding: 0 6px; font-size: 11px; }
|
||||
|
||||
.branch-context-menu,
|
||||
.explorer-context-menu {
|
||||
@@ -3687,6 +3738,7 @@
|
||||
.history-resize-handle { display: none; }
|
||||
.shell-body { gap: 6px; }
|
||||
.left-sidebar { gap: 6px; grid-template-rows: minmax(150px, 0.7fr) minmax(145px, 0.55fr) minmax(190px, 1fr); }
|
||||
.left-sidebar:has(.stash-panel.collapsed) { grid-template-rows: minmax(150px, 0.8fr) auto minmax(190px, 1.1fr); }
|
||||
.section-head { min-height: 40px; padding: 6px 10px; }
|
||||
.repo-summary { height: 40px; padding: 0 10px; }
|
||||
.repo-branch { max-width: 160px; }
|
||||
@@ -3704,6 +3756,7 @@
|
||||
.workspace { grid-template-columns: 1fr; gap: 6px; }
|
||||
.history-aside { grid-template-columns: 1fr; grid-template-rows: minmax(200px, 1fr) minmax(150px, 0.5fr); min-height: 380px; }
|
||||
.left-sidebar { grid-template-rows: minmax(180px, 0.9fr) minmax(150px, 0.55fr) minmax(220px, 1fr); min-height: 560px; }
|
||||
.left-sidebar:has(.stash-panel.collapsed) { grid-template-rows: minmax(180px, 1fr) auto minmax(220px, 1.1fr); }
|
||||
.top-section { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) 14px var(--commit-panel-height, 190px); }
|
||||
.repo-form { grid-template-columns: 1fr; }
|
||||
.repo-tabbar { grid-template-columns: minmax(0, 1fr) auto; }
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
isBusy: boolean;
|
||||
onCheckout: (branch: GitBranchInfo) => void;
|
||||
onMerge: (branch: GitBranchInfo) => void;
|
||||
onRebase: (branch: GitBranchInfo) => void;
|
||||
onCreateBranch: (branchName: string) => void | Promise<void>;
|
||||
onRenameBranch: (branch: GitBranchInfo) => void | Promise<void>;
|
||||
onDeleteBranch: (branch: GitBranchInfo) => void | Promise<void>;
|
||||
@@ -62,6 +63,7 @@
|
||||
isBusy = false,
|
||||
onCheckout = () => {},
|
||||
onMerge = () => {},
|
||||
onRebase = () => {},
|
||||
onCreateBranch = () => {},
|
||||
onRenameBranch = () => {},
|
||||
onDeleteBranch = () => {},
|
||||
@@ -370,6 +372,10 @@
|
||||
<GitMerge size={15} aria-hidden="true" />
|
||||
Merge
|
||||
</button>
|
||||
<button class="btn-sm" type="button" onclick={() => onRebase(row.branch)} disabled={isBusy} title="Rebase current branch onto this branch">
|
||||
<GitBranch size={15} aria-hidden="true" />
|
||||
Rebase
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</article>
|
||||
@@ -446,6 +452,10 @@
|
||||
<GitMerge size={15} aria-hidden="true" />
|
||||
Merge
|
||||
</button>
|
||||
<button class="btn-sm" type="button" onclick={() => onRebase(row.branch)} disabled={isBusy} title="Rebase current branch onto this branch">
|
||||
<GitBranch size={15} aria-hidden="true" />
|
||||
Rebase
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</article>
|
||||
|
||||
@@ -261,13 +261,13 @@
|
||||
style="grid-template-rows: auto minmax(0,1fr) auto;"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Resolve merge conflicts"
|
||||
aria-label="Resolve conflicts"
|
||||
tabindex="-1"
|
||||
>
|
||||
<header class="dialog-header">
|
||||
<div>
|
||||
<span class="eyebrow">Resolve</span>
|
||||
<h2 class="dialog-title">Merge conflicts</h2>
|
||||
<h2 class="dialog-title">Conflicts</h2>
|
||||
</div>
|
||||
<button class="dialog-close" type="button" onclick={onClose} title="Close">
|
||||
<X size={18} aria-hidden="true" />
|
||||
@@ -275,7 +275,7 @@
|
||||
</header>
|
||||
|
||||
{#if conflictedFiles.length === 0}
|
||||
<div class="blank-state">All conflicts resolved. You can commit the merge now.</div>
|
||||
<div class="blank-state">All conflicts resolved. Continue the current operation when ready.</div>
|
||||
{:else}
|
||||
<div class="dialog-body">
|
||||
<aside class="dialog-files" aria-label="Conflicted files">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Archive, Download, Trash2, Upload } from "@lucide/svelte";
|
||||
import { Archive, ChevronDown, ChevronRight, Download, Trash2, Upload } from "@lucide/svelte";
|
||||
import type { GitStash } from "../types";
|
||||
|
||||
interface Props {
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
let message = $state("");
|
||||
let includeUntracked = $state(true);
|
||||
let open = $state(false);
|
||||
|
||||
function submitPush() {
|
||||
onPush(message, includeUntracked);
|
||||
@@ -37,16 +38,33 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="panel stash-panel overflow-hidden" aria-label="Git stash">
|
||||
<section class="panel stash-panel overflow-hidden" class:collapsed={!open} aria-label="Git stash">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<span class="eyebrow">Stash</span>
|
||||
<h2 class="mt-0.5 text-ink text-base font-bold leading-tight">Shelved changes</h2>
|
||||
</div>
|
||||
<span class="pill pill-count">{stashes.length}</span>
|
||||
<div class="stash-head-actions">
|
||||
<button
|
||||
class="stash-toggle"
|
||||
type="button"
|
||||
onclick={() => { open = !open; }}
|
||||
aria-expanded={open}
|
||||
title={open ? "Collapse stash panel" : "Expand stash panel"}
|
||||
>
|
||||
{#if open}
|
||||
<ChevronDown size={14} aria-hidden="true" />
|
||||
{:else}
|
||||
<ChevronRight size={14} aria-hidden="true" />
|
||||
{/if}
|
||||
</button>
|
||||
<span class="pill pill-count">{stashes.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !hasRepository}
|
||||
{#if !open}
|
||||
<!-- collapsed -->
|
||||
{:else if !hasRepository}
|
||||
<div class="blank-state">No repository loaded.</div>
|
||||
{:else}
|
||||
<div class="stash-create">
|
||||
|
||||
@@ -219,6 +219,18 @@ export function mergeBranch(path: string, branch: string): Promise<GitStatus> {
|
||||
return invoke<GitStatus>("merge_branch", { path, branch });
|
||||
}
|
||||
|
||||
export function rebaseBranch(path: string, branch: string): Promise<GitStatus> {
|
||||
return invoke<GitStatus>("rebase_branch", { path, branch });
|
||||
}
|
||||
|
||||
export function rebaseContinue(path: string): Promise<GitStatus> {
|
||||
return invoke<GitStatus>("rebase_continue", { path });
|
||||
}
|
||||
|
||||
export function rebaseAbort(path: string): Promise<GitStatus> {
|
||||
return invoke<GitStatus>("rebase_abort", { path });
|
||||
}
|
||||
|
||||
export function listRepositoryFiles(path: string): Promise<GitRepositoryFile[]> {
|
||||
return invoke<GitRepositoryFile[]>("list_repository_files", { path });
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface GitStatus {
|
||||
behind: number;
|
||||
files: GitFileStatus[];
|
||||
clean: boolean;
|
||||
rebase_in_progress: boolean;
|
||||
}
|
||||
|
||||
export interface GitFileStatus {
|
||||
|
||||
Reference in New Issue
Block a user