feat(git): convert Git commands to async for better performance

This update modifies several Git command functions to be asynchronous,
improving the responsiveness of the application. By utilizing async
runtime, operations that involve I/O or long-running tasks can now run
without blocking the main thread, enhancing user experience.

- Converted multiple Git command functions to async
- Introduced a helper function to handle async tasks with error management
- Improved overall performance and responsiveness of Git operations
This commit is contained in:
Christoph Brandau
2026-08-11 09:53:34 +02:00
parent 444a7acadd
commit 1fa57eea6f
2 changed files with 494 additions and 315 deletions
+347 -155
View File
@@ -382,13 +382,13 @@ fn check_search_cancelled(cancellation: Option<&SearchCancellation>) -> Result<(
Ok(())
}
#[tauri::command]
#[tauri::command(async)]
pub fn open_repository(path: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
status_for_repo(&repo)
}
#[tauri::command]
#[tauri::command(async)]
pub fn init_repository(path: String, initial_branch: Option<String>) -> Result<GitStatus, String> {
let path = PathBuf::from(path.trim());
if path.as_os_str().is_empty() {
@@ -416,13 +416,13 @@ pub fn init_repository(path: String, initial_branch: Option<String>) -> Result<G
status_for_repo(&path)
}
#[tauri::command]
#[tauri::command(async)]
pub fn open_repo_in_explorer(path: String) -> Result<(), String> {
let repo = resolve_repo(&path)?;
open_path_in_file_manager(&repo)
}
#[tauri::command]
#[tauri::command(async)]
pub fn open_repository_file(path: String, file: String) -> Result<(), String> {
let repo = resolve_repo(&path)?;
validate_files(std::slice::from_ref(&file))?;
@@ -448,6 +448,56 @@ pub struct RepositoryBundle {
pub files: Vec<GitRepositoryFile>,
}
async fn run_git_task<T, F>(context: &'static str, task: F) -> Result<T, String>
where
T: Send + 'static,
F: FnOnce() -> Result<T, String> + Send + 'static,
{
tauri::async_runtime::spawn_blocking(task)
.await
.map_err(|error| format!("{context}: {error}"))?
}
fn join_git_worker<T>(name: &str, result: thread::Result<Result<T, String>>) -> Result<T, String> {
result.map_err(|_| format!("The {name} Git worker stopped unexpectedly."))?
}
fn repository_bundle_for_repo(
repo: &Path,
commit_limit: Option<u32>,
) -> Result<RepositoryBundle, String> {
// Status is needed by the file tree. Once it is available, all remaining
// reads are independent and can run concurrently. Each worker only starts
// read-only Git processes, so this is safe while cutting the former
// branches -> tags -> stashes -> commits -> files waterfall down to the
// duration of its slowest member.
let status = status_for_repo(repo)?;
let (branches, tags, stashes, commits, files) = thread::scope(|scope| -> Result<_, String> {
let branches = scope.spawn(|| branches_for_repo(repo));
let tags = scope.spawn(|| tags_for_repo(repo));
let stashes = scope.spawn(|| stashes_for_repo(repo));
let commits = scope.spawn(|| commits_for_repo(repo, commit_limit));
let files = scope.spawn(|| repository_files_with_status(repo, &status));
Ok((
join_git_worker("branch", branches.join())?,
join_git_worker("tag", tags.join())?,
join_git_worker("stash", stashes.join())?,
join_git_worker("history", commits.join())?,
join_git_worker("file tree", files.join())?,
))
})?;
Ok(RepositoryBundle {
status,
branches,
tags,
stashes,
commits,
files,
})
}
#[tauri::command]
pub async fn clone_repository(
remote_url: String,
@@ -481,40 +531,32 @@ pub async fn open_repository_bundle(
path: String,
commit_limit: Option<u32>,
) -> Result<RepositoryBundle, String> {
tauri::async_runtime::spawn_blocking(move || -> Result<RepositoryBundle, String> {
run_git_task("Could not load repository", move || {
let repo = resolve_repo(&path)?;
let status = status_for_repo(&repo)?;
let branches = branches_for_repo(&repo)?;
let tags = tags_for_repo(&repo)?;
let stashes = stashes_for_repo(&repo)?;
let commits = commits_for_repo(&repo, commit_limit)?;
let files = repository_files_with_status(&repo, &status)?;
Ok(RepositoryBundle {
status,
branches,
tags,
stashes,
commits,
files,
})
repository_bundle_for_repo(&repo, commit_limit)
})
.await
.map_err(|err| format!("Could not load repository: {err}"))?
}
#[tauri::command]
pub fn get_status(path: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
status_for_repo(&repo)
pub async fn get_status(path: String) -> Result<GitStatus, String> {
run_git_task("Could not refresh repository status", move || {
let repo = resolve_repo(&path)?;
status_for_repo(&repo)
})
.await
}
#[tauri::command]
pub fn list_branches(path: String) -> Result<Vec<GitBranch>, String> {
let repo = resolve_repo(&path)?;
branches_for_repo(&repo)
pub async fn list_branches(path: String) -> Result<Vec<GitBranch>, String> {
run_git_task("Could not load branches", move || {
let repo = resolve_repo(&path)?;
branches_for_repo(&repo)
})
.await
}
#[tauri::command]
#[tauri::command(async)]
pub fn list_remotes(path: String) -> Result<Vec<GitRemote>, String> {
log::info!(target: "gitty::remote", "list_remotes invoked: path={path:?}");
let repo = resolve_repo(&path)?;
@@ -535,7 +577,7 @@ pub fn list_remotes(path: String) -> Result<Vec<GitRemote>, String> {
.collect())
}
#[tauri::command]
#[tauri::command(async)]
pub fn add_remote(path: String, name: String, url: String) -> Result<Vec<GitRemote>, String> {
let repo = resolve_repo(&path)?;
let name = validate_remote_name(&repo, &name, false)?;
@@ -544,7 +586,7 @@ pub fn add_remote(path: String, name: String, url: String) -> Result<Vec<GitRemo
list_remotes(path)
}
#[tauri::command]
#[tauri::command(async)]
pub fn update_remote(path: String, name: String, url: String) -> Result<Vec<GitRemote>, String> {
let repo = resolve_repo(&path)?;
let name = validate_remote_name(&repo, &name, true)?;
@@ -553,7 +595,7 @@ pub fn update_remote(path: String, name: String, url: String) -> Result<Vec<GitR
list_remotes(path)
}
#[tauri::command]
#[tauri::command(async)]
pub fn remove_remote(path: String, name: String) -> Result<Vec<GitRemote>, String> {
log::info!(target: "gitty::remote", "remove_remote invoked: path={path:?}, name={name:?}");
let result = (|| {
@@ -574,7 +616,7 @@ pub fn remove_remote(path: String, name: String) -> Result<Vec<GitRemote>, Strin
result
}
#[tauri::command]
#[tauri::command(async)]
pub fn set_branch_upstream(
path: String,
branch: String,
@@ -607,7 +649,7 @@ pub fn set_branch_upstream(
status_for_repo(&repo)
}
#[tauri::command]
#[tauri::command(async)]
pub fn delete_remote_branch(
path: String,
remote: String,
@@ -634,15 +676,21 @@ pub fn delete_remote_branch(
}
#[tauri::command]
pub fn list_stashes(path: String) -> Result<Vec<GitStash>, String> {
let repo = resolve_repo(&path)?;
stashes_for_repo(&repo)
pub async fn list_stashes(path: String) -> Result<Vec<GitStash>, String> {
run_git_task("Could not load stashes", move || {
let repo = resolve_repo(&path)?;
stashes_for_repo(&repo)
})
.await
}
#[tauri::command]
pub fn list_tags(path: String) -> Result<Vec<GitTag>, String> {
let repo = resolve_repo(&path)?;
tags_for_repo(&repo)
pub async fn list_tags(path: String) -> Result<Vec<GitTag>, String> {
run_git_task("Could not load tags", move || {
let repo = resolve_repo(&path)?;
tags_for_repo(&repo)
})
.await
}
fn tags_for_repo(repo: &Path) -> Result<Vec<GitTag>, String> {
@@ -790,39 +838,51 @@ fn parse_stash_subject(subject: &str) -> (Option<String>, String) {
}
#[tauri::command]
pub fn stash_push(
pub async fn stash_push(
path: String,
message: Option<String>,
include_untracked: bool,
) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
let trimmed_message = message.unwrap_or_default().trim().to_string();
let mut args: Vec<OsString> = vec![OsString::from("stash"), OsString::from("push")];
if include_untracked {
args.push(OsString::from("--include-untracked"));
}
if !trimmed_message.is_empty() {
args.push(OsString::from("-m"));
args.push(OsString::from(trimmed_message));
}
run_git_task("Could not stash changes", move || {
let repo = resolve_repo(&path)?;
let trimmed_message = message.unwrap_or_default().trim().to_string();
let mut args: Vec<OsString> = vec![OsString::from("stash"), OsString::from("push")];
if include_untracked {
args.push(OsString::from("--include-untracked"));
}
if !trimmed_message.is_empty() {
args.push(OsString::from("-m"));
args.push(OsString::from(trimmed_message));
}
run_git(&repo, args)?;
status_for_repo(&repo)
run_git(&repo, args)?;
status_for_repo(&repo)
})
.await
}
#[tauri::command]
pub fn stash_apply(path: String, selector: String) -> Result<GitStatus, String> {
run_stash_update(path, "apply", selector)
pub async fn stash_apply(path: String, selector: String) -> Result<GitStatus, String> {
run_git_task("Could not apply stash", move || {
run_stash_update(path, "apply", selector)
})
.await
}
#[tauri::command]
pub fn stash_pop(path: String, selector: String) -> Result<GitStatus, String> {
run_stash_update(path, "pop", selector)
pub async fn stash_pop(path: String, selector: String) -> Result<GitStatus, String> {
run_git_task("Could not pop stash", move || {
run_stash_update(path, "pop", selector)
})
.await
}
#[tauri::command]
pub fn stash_drop(path: String, selector: String) -> Result<GitStatus, String> {
run_stash_update(path, "drop", selector)
pub async fn stash_drop(path: String, selector: String) -> Result<GitStatus, String> {
run_git_task("Could not drop stash", move || {
run_stash_update(path, "drop", selector)
})
.await
}
fn run_stash_update(path: String, action: &str, selector: String) -> Result<GitStatus, String> {
@@ -859,7 +919,14 @@ fn validate_stash_selector(selector: &str) -> Result<String, String> {
}
#[tauri::command]
pub fn checkout_branch(path: String, branch: String) -> Result<GitStatus, String> {
pub async fn checkout_branch(path: String, branch: String) -> Result<GitStatus, String> {
run_git_task("Could not check out branch", move || {
checkout_branch_core(path, branch)
})
.await
}
fn checkout_branch_core(path: String, branch: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
let branch = branch.trim().to_string();
if branch.is_empty() {
@@ -885,7 +952,18 @@ pub fn checkout_branch(path: String, branch: String) -> Result<GitStatus, String
}
#[tauri::command]
pub fn create_branch(
pub async fn create_branch(
path: String,
branch: String,
start_point: Option<String>,
) -> Result<GitStatus, String> {
run_git_task("Could not create branch", move || {
create_branch_core(path, branch, start_point)
})
.await
}
fn create_branch_core(
path: String,
branch: String,
start_point: Option<String>,
@@ -906,7 +984,18 @@ pub fn create_branch(
}
#[tauri::command]
pub fn rename_branch(
pub async fn rename_branch(
path: String,
old_branch: String,
new_branch: String,
) -> Result<GitStatus, String> {
run_git_task("Could not rename branch", move || {
rename_branch_core(path, old_branch, new_branch)
})
.await
}
fn rename_branch_core(
path: String,
old_branch: String,
new_branch: String,
@@ -929,7 +1018,18 @@ pub fn rename_branch(
}
#[tauri::command]
pub fn delete_branch(
pub async fn delete_branch(
path: String,
branch: String,
force: Option<bool>,
) -> Result<GitStatus, String> {
run_git_task("Could not delete branch", move || {
delete_branch_core(path, branch, force)
})
.await
}
fn delete_branch_core(
path: String,
branch: String,
force: Option<bool>,
@@ -1070,13 +1170,43 @@ fn worktrees_for_repo(repo: &Path) -> Result<Vec<GitWorktree>, String> {
}
#[tauri::command]
pub fn list_worktrees(path: String) -> Result<Vec<GitWorktree>, String> {
pub async fn list_worktrees(path: String) -> Result<Vec<GitWorktree>, String> {
run_git_task("Could not load worktrees", move || {
list_worktrees_core(path)
})
.await
}
fn list_worktrees_core(path: String) -> Result<Vec<GitWorktree>, String> {
let repo = resolve_repo(&path)?;
worktrees_for_repo(&repo)
}
#[tauri::command]
pub fn add_worktree(
pub async fn add_worktree(
path: String,
worktree_path: String,
branch: Option<String>,
new_branch: Option<String>,
start_point: Option<String>,
detached: Option<bool>,
lock: Option<bool>,
) -> Result<Vec<GitWorktree>, String> {
run_git_task("Could not add worktree", move || {
add_worktree_core(
path,
worktree_path,
branch,
new_branch,
start_point,
detached,
lock,
)
})
.await
}
fn add_worktree_core(
path: String,
worktree_path: String,
branch: Option<String>,
@@ -1136,7 +1266,18 @@ pub fn add_worktree(
}
#[tauri::command]
pub fn remove_worktree(
pub async fn remove_worktree(
path: String,
worktree_path: String,
force: Option<bool>,
) -> Result<Vec<GitWorktree>, String> {
run_git_task("Could not remove worktree", move || {
remove_worktree_core(path, worktree_path, force)
})
.await
}
fn remove_worktree_core(
path: String,
worktree_path: String,
force: Option<bool>,
@@ -1168,7 +1309,18 @@ pub fn remove_worktree(
}
#[tauri::command]
pub fn move_worktree(
pub async fn move_worktree(
path: String,
worktree_path: String,
destination: String,
) -> Result<Vec<GitWorktree>, String> {
run_git_task("Could not move worktree", move || {
move_worktree_core(path, worktree_path, destination)
})
.await
}
fn move_worktree_core(
path: String,
worktree_path: String,
destination: String,
@@ -1191,7 +1343,18 @@ pub fn move_worktree(
}
#[tauri::command]
pub fn lock_worktree(
pub async fn lock_worktree(
path: String,
worktree_path: String,
reason: Option<String>,
) -> Result<Vec<GitWorktree>, String> {
run_git_task("Could not lock worktree", move || {
lock_worktree_core(path, worktree_path, reason)
})
.await
}
fn lock_worktree_core(
path: String,
worktree_path: String,
reason: Option<String>,
@@ -1211,28 +1374,59 @@ pub fn lock_worktree(
}
#[tauri::command]
pub fn unlock_worktree(path: String, worktree_path: String) -> Result<Vec<GitWorktree>, String> {
pub async fn unlock_worktree(
path: String,
worktree_path: String,
) -> Result<Vec<GitWorktree>, String> {
run_git_task("Could not unlock worktree", move || {
unlock_worktree_core(path, worktree_path)
})
.await
}
fn unlock_worktree_core(path: String, worktree_path: String) -> Result<Vec<GitWorktree>, String> {
let repo = resolve_repo(&path)?;
run_git(&repo, ["worktree", "unlock", "--", worktree_path.as_str()])?;
worktrees_for_repo(&repo)
}
#[tauri::command]
pub fn prune_worktrees(path: String) -> Result<Vec<GitWorktree>, String> {
let repo = resolve_repo(&path)?;
run_git(&repo, ["worktree", "prune"])?;
worktrees_for_repo(&repo)
pub async fn prune_worktrees(path: String) -> Result<Vec<GitWorktree>, String> {
run_git_task("Could not prune worktrees", move || {
let repo = resolve_repo(&path)?;
run_git(&repo, ["worktree", "prune"])?;
worktrees_for_repo(&repo)
})
.await
}
#[tauri::command]
pub fn repair_worktree(path: String, worktree_path: String) -> Result<Vec<GitWorktree>, String> {
let repo = resolve_repo(&path)?;
run_git(&repo, ["worktree", "repair", "--", worktree_path.as_str()])?;
worktrees_for_repo(&repo)
pub async fn repair_worktree(
path: String,
worktree_path: String,
) -> Result<Vec<GitWorktree>, String> {
run_git_task("Could not repair worktree", move || {
let repo = resolve_repo(&path)?;
run_git(&repo, ["worktree", "repair", "--", worktree_path.as_str()])?;
worktrees_for_repo(&repo)
})
.await
}
#[tauri::command]
pub fn create_tag(
pub async fn create_tag(
path: String,
name: String,
target: Option<String>,
message: Option<String>,
) -> Result<Vec<GitTag>, String> {
run_git_task("Could not create tag", move || {
create_tag_core(path, name, target, message)
})
.await
}
fn create_tag_core(
path: String,
name: String,
target: Option<String>,
@@ -1271,36 +1465,42 @@ pub fn create_tag(
}
#[tauri::command]
pub fn delete_tag(path: String, name: String) -> Result<Vec<GitTag>, String> {
let repo = resolve_repo(&path)?;
let name = validate_existing_tag_name(&repo, &name)?;
run_git(&repo, ["tag", "-d", name.as_str()])?;
tags_for_repo(&repo)
pub async fn delete_tag(path: String, name: String) -> Result<Vec<GitTag>, String> {
run_git_task("Could not delete tag", move || {
let repo = resolve_repo(&path)?;
let name = validate_existing_tag_name(&repo, &name)?;
run_git(&repo, ["tag", "-d", name.as_str()])?;
tags_for_repo(&repo)
})
.await
}
#[tauri::command]
pub fn push_tag(
pub async fn push_tag(
path: String,
name: String,
username: Option<String>,
password: Option<String>,
) -> Result<(), String> {
let repo = resolve_repo(&path)?;
let name = validate_existing_tag_name(&repo, &name)?;
let remote = initial_push_remote_name(&repo)?;
let tag_ref = format!("refs/tags/{name}");
let push_args = ["push", remote.as_str(), tag_ref.as_str()];
run_git_task("Could not push tag", move || {
let repo = resolve_repo(&path)?;
let name = validate_existing_tag_name(&repo, &name)?;
let remote = initial_push_remote_name(&repo)?;
let tag_ref = format!("refs/tags/{name}");
let push_args = ["push", remote.as_str(), tag_ref.as_str()];
match (username.as_deref(), password.as_deref()) {
(Some(u), Some(p)) if !u.is_empty() || !p.is_empty() => {
run_git_authenticated(&repo, push_args, u, p)?;
match (username.as_deref(), password.as_deref()) {
(Some(u), Some(p)) if !u.is_empty() || !p.is_empty() => {
run_git_authenticated(&repo, push_args, u, p)?;
}
_ => {
run_git(&repo, push_args)?;
}
}
_ => {
run_git(&repo, push_args)?;
}
}
Ok(())
Ok(())
})
.await
}
fn validate_new_tag_name(repo: &Path, name: &str) -> Result<String, String> {
@@ -1435,7 +1635,7 @@ pub async fn restore_files(
.map_err(|err| format!("Could not restore files: {err}"))?
}
#[tauri::command]
#[tauri::command(async)]
pub fn get_file_patch(path: String, file: String, staged: bool) -> Result<String, String> {
let repo = resolve_repo(&path)?;
validate_files(std::slice::from_ref(&file))?;
@@ -1866,7 +2066,7 @@ pub async fn commit_ai_review(
parse_ai_review(&raw)
}
#[tauri::command]
#[tauri::command(async)]
pub fn apply_file_patch(
path: String,
file: String,
@@ -1951,7 +2151,7 @@ pub async fn amend_commit(path: String, message: Option<String>) -> Result<GitSt
.map_err(|err| format!("Could not amend commit: {err}"))?
}
#[tauri::command]
#[tauri::command(async)]
pub fn last_commit_message(path: String) -> Result<Option<String>, String> {
let repo = resolve_repo(&path)?;
if verify_commit(&repo, "HEAD").is_err() {
@@ -1967,7 +2167,7 @@ pub fn last_commit_message(path: String) -> Result<Option<String>, String> {
})
}
#[tauri::command]
#[tauri::command(async)]
pub fn undo_last_commit(path: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
if verify_commit(&repo, "HEAD").is_err() {
@@ -2146,7 +2346,7 @@ fn cred_entry(key: &str) -> Result<keyring::Entry, String> {
/// Returns the remote URL used for auth key derivation (upstream remote of the
/// current branch, falling back to `origin`, then the first configured remote).
#[tauri::command]
#[tauri::command(async)]
pub fn get_remote_url(path: String) -> Result<Option<String>, String> {
let repo = resolve_repo(&path)?;
let remote = upstream_remote_name(&repo).unwrap_or_else(|| "origin".to_string());
@@ -2301,7 +2501,7 @@ fn push_args_for_repo_to(
])
}
#[tauri::command]
#[tauri::command(async)]
pub fn cred_load(key: String) -> Result<Option<StoredCredential>, String> {
let entry = cred_entry(&key)?;
match entry.get_password() {
@@ -2315,7 +2515,7 @@ pub fn cred_load(key: String) -> Result<Option<StoredCredential>, String> {
}
}
#[tauri::command]
#[tauri::command(async)]
pub fn cred_save(
key: String,
username: String,
@@ -2336,7 +2536,7 @@ pub fn cred_save(
.map_err(|err| format!("Saving to keychain failed: {err}"))
}
#[tauri::command]
#[tauri::command(async)]
pub fn cred_delete(key: String) -> Result<(), String> {
let entry = cred_entry(&key)?;
match entry.delete_credential() {
@@ -2402,7 +2602,7 @@ pub async fn merge_branch(
.map_err(|err| format!("Could not merge: {err}"))?
}
#[tauri::command]
#[tauri::command(async)]
pub fn merge_continue(path: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
if !merge_in_progress(&repo) {
@@ -2415,7 +2615,7 @@ pub fn merge_continue(path: String) -> Result<GitStatus, String> {
status_for_repo(&repo)
}
#[tauri::command]
#[tauri::command(async)]
pub fn merge_abort(path: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
if !merge_in_progress(&repo) {
@@ -2425,7 +2625,7 @@ pub fn merge_abort(path: String) -> Result<GitStatus, String> {
status_for_repo(&repo)
}
#[tauri::command]
#[tauri::command(async)]
pub fn revert_commit(path: String, commit: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
let commit = verify_commit(&repo, &commit)?;
@@ -2470,7 +2670,7 @@ pub async fn rebase_branch(path: String, branch: String) -> Result<GitStatus, St
.map_err(|err| format!("Could not rebase: {err}"))?
}
#[tauri::command]
#[tauri::command(async)]
pub fn list_interactive_rebase_commits(
path: String,
base: String,
@@ -2550,7 +2750,7 @@ pub async fn start_interactive_rebase(
.map_err(|err| format!("Could not run interactive rebase: {err}"))?
}
#[tauri::command]
#[tauri::command(async)]
pub fn rebase_continue(path: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
if !rebase_in_progress(&repo) {
@@ -2580,7 +2780,7 @@ pub fn rebase_continue(path: String) -> Result<GitStatus, String> {
result
}
#[tauri::command]
#[tauri::command(async)]
pub fn rebase_abort(path: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
if !rebase_in_progress(&repo) {
@@ -2592,7 +2792,7 @@ pub fn rebase_abort(path: String) -> Result<GitStatus, String> {
status_for_repo(&repo)
}
#[tauri::command]
#[tauri::command(async)]
pub fn list_reflog(path: String, limit: Option<u32>) -> Result<Vec<ReflogEntry>, String> {
let repo = resolve_repo(&path)?;
let limit = limit.unwrap_or(250).clamp(1, 1_000).to_string();
@@ -2610,7 +2810,7 @@ pub fn list_reflog(path: String, limit: Option<u32>) -> Result<Vec<ReflogEntry>,
parse_reflog(&output)
}
#[tauri::command]
#[tauri::command(async)]
pub fn restore_reflog_entry(
path: String,
commit: String,
@@ -2870,7 +3070,7 @@ pub async fn cherry_pick_commit(path: String, commit: String) -> Result<GitStatu
.map_err(|err| format!("Could not cherry-pick: {err}"))?
}
#[tauri::command]
#[tauri::command(async)]
pub fn cherry_pick_continue(path: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
if !cherry_pick_in_progress(&repo) {
@@ -2888,7 +3088,7 @@ pub fn cherry_pick_continue(path: String) -> Result<GitStatus, String> {
cherry_pick_status_or_error(&repo, output, "Cherry-pick continue failed")
}
#[tauri::command]
#[tauri::command(async)]
pub fn cherry_pick_abort(path: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
if !cherry_pick_in_progress(&repo) {
@@ -2917,13 +3117,16 @@ fn cherry_pick_status_or_error(
}
#[tauri::command]
pub fn list_commits(
pub async fn list_commits(
path: String,
limit: Option<u32>,
skip: Option<u32>,
) -> Result<Vec<GitCommit>, String> {
let repo = resolve_repo(&path)?;
commit_page_for_repo(&repo, limit, skip)
run_git_task("Could not load commit history", move || {
let repo = resolve_repo(&path)?;
commit_page_for_repo(&repo, limit, skip)
})
.await
}
fn commits_for_repo(repo: &Path, limit: Option<u32>) -> Result<Vec<GitCommit>, String> {
@@ -2968,9 +3171,12 @@ fn commit_page_for_repo(
}
#[tauri::command]
pub fn list_repository_files(path: String) -> Result<Vec<GitRepositoryFile>, String> {
let repo = resolve_repo(&path)?;
repository_files(&repo)
pub async fn list_repository_files(path: String) -> Result<Vec<GitRepositoryFile>, String> {
run_git_task("Could not load repository files", move || {
let repo = resolve_repo(&path)?;
repository_files(&repo)
})
.await
}
#[tauri::command]
@@ -3020,7 +3226,7 @@ pub fn cancel_file_history(
const UNCOMMITTED_BLAME_HASH: &str = "0000000000000000000000000000000000000000";
#[tauri::command]
#[tauri::command(async)]
pub fn get_file_blame(path: String, file: String) -> Result<GitBlameResult, String> {
let repo = resolve_repo(&path)?;
validate_files(std::slice::from_ref(&file))?;
@@ -3318,7 +3524,7 @@ fn search_code_introductions_core(
Ok(hits)
}
#[tauri::command]
#[tauri::command(async)]
pub fn restore_to_commit(path: String, commit: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
let commit_hash = verify_commit(&repo, &commit)?;
@@ -3341,7 +3547,7 @@ pub fn restore_to_commit(path: String, commit: String) -> Result<GitStatus, Stri
status_for_repo(&repo)
}
#[tauri::command]
#[tauri::command(async)]
pub fn restore_file_from_commit(
path: String,
commit: String,
@@ -3359,7 +3565,7 @@ pub fn restore_file_from_commit(
status_for_repo(&repo)
}
#[tauri::command]
#[tauri::command(async)]
pub fn compare_commits(
path: String,
from: String,
@@ -3417,7 +3623,7 @@ pub fn compare_commits(
})
}
#[tauri::command]
#[tauri::command(async)]
pub fn diff_file_against_working_tree(
path: String,
commit: String,
@@ -3463,7 +3669,7 @@ pub fn diff_file_against_working_tree(
})
}
#[tauri::command]
#[tauri::command(async)]
pub fn compare_file_to_head(
path: String,
commit: String,
@@ -3525,7 +3731,7 @@ pub fn compare_file_to_head(
})
}
#[tauri::command]
#[tauri::command(async)]
pub fn compare_file_to_parent(
path: String,
commit: String,
@@ -3599,7 +3805,7 @@ pub fn compare_file_to_parent(
})
}
#[tauri::command]
#[tauri::command(async)]
pub fn read_conflict(path: String, file: String) -> Result<ConflictFile, String> {
let repo = resolve_repo(&path)?;
validate_files(std::slice::from_ref(&file))?;
@@ -3635,7 +3841,7 @@ pub fn read_conflict(path: String, file: String) -> Result<ConflictFile, String>
})
}
#[tauri::command]
#[tauri::command(async)]
pub fn resolve_conflict_side(
path: String,
file: String,
@@ -3677,7 +3883,7 @@ fn index_stage_size(repo: &Path, stage: u8, file: &str) -> Option<u64> {
}
}
#[tauri::command]
#[tauri::command(async)]
pub fn resolve_conflict(path: String, file: String, content: String) -> Result<GitStatus, String> {
let repo = resolve_repo(&path)?;
validate_files(std::slice::from_ref(&file))?;
@@ -4073,21 +4279,7 @@ fn clone_repository_core(
run_git_clone(remote_url.trim(), &target, username, password)?;
let repo = resolve_repo(&target.to_string_lossy())?;
let status = status_for_repo(&repo)?;
let branches = branches_for_repo(&repo)?;
let tags = tags_for_repo(&repo)?;
let stashes = stashes_for_repo(&repo)?;
let commits = commits_for_repo(&repo, commit_limit)?;
let files = repository_files_with_status(&repo, &status)?;
Ok(RepositoryBundle {
status,
branches,
tags,
stashes,
commits,
files,
})
repository_bundle_for_repo(&repo, commit_limit)
}
fn clone_target_path(
@@ -6747,7 +6939,7 @@ mod tests {
let repo = init_temp_repo("create_branch");
commit_initial_file(&repo.path);
let status = create_branch(
let status = create_branch_core(
repo.path.to_string_lossy().to_string(),
"feature/new-panel".to_string(),
None,
@@ -6760,7 +6952,7 @@ mod tests {
"new branch should exist"
);
let err = create_branch(
let err = create_branch_core(
repo.path.to_string_lossy().to_string(),
"feature/new-panel".to_string(),
None,
@@ -6776,7 +6968,7 @@ mod tests {
let current = git_output_test(&repo.path, ["rev-parse", "--abbrev-ref", "HEAD"]);
run_git_test(&repo.path, ["branch", "feature/old-panel"]);
let status = rename_branch(
let status = rename_branch_core(
repo.path.to_string_lossy().to_string(),
"feature/old-panel".to_string(),
"feature/new-panel".to_string(),
@@ -6801,7 +6993,7 @@ mod tests {
let current = git_output_test(&repo.path, ["rev-parse", "--abbrev-ref", "HEAD"]);
run_git_test(&repo.path, ["branch", "stale"]);
let status = delete_branch(
let status = delete_branch_core(
repo.path.to_string_lossy().to_string(),
"stale".to_string(),
None,
@@ -6815,7 +7007,7 @@ mod tests {
);
let err =
delete_branch(repo.path.to_string_lossy().to_string(), current, None).unwrap_err();
delete_branch_core(repo.path.to_string_lossy().to_string(), current, None).unwrap_err();
assert!(err.contains("current branch"));
}
@@ -6831,7 +7023,7 @@ mod tests {
run_git_test(&repo.path, ["commit", "-q", "-m", "feature"]);
run_git_test(&repo.path, ["checkout", "-q", current.as_str()]);
let err = delete_branch(
let err = delete_branch_core(
repo.path.to_string_lossy().to_string(),
"feature/unmerged".to_string(),
Some(false),
@@ -6839,7 +7031,7 @@ mod tests {
.unwrap_err();
assert!(err.contains("not fully merged"));
delete_branch(
delete_branch_core(
repo.path.to_string_lossy().to_string(),
"feature/unmerged".to_string(),
Some(true),
@@ -7216,7 +7408,7 @@ mod tests {
commit_initial_file(&repo.path);
run_git_test(&repo.path, ["branch", "feature"]);
let rows = add_worktree(
let rows = add_worktree_core(
repo.path.to_string_lossy().to_string(),
destination.path.to_string_lossy().to_string(),
Some("feature".to_string()),
@@ -7234,7 +7426,7 @@ mod tests {
assert!(!linked.is_main);
assert!(linked.clean);
let rows = lock_worktree(
let rows = lock_worktree_core(
repo.path.to_string_lossy().to_string(),
destination.path.to_string_lossy().to_string(),
Some("test lock".to_string()),
@@ -7247,13 +7439,13 @@ mod tests {
assert!(linked.locked);
assert_eq!(linked.lock_reason.as_deref(), Some("test lock"));
unlock_worktree(
unlock_worktree_core(
repo.path.to_string_lossy().to_string(),
destination.path.to_string_lossy().to_string(),
)
.expect("worktree should unlock");
let rows = remove_worktree(
let rows = remove_worktree_core(
repo.path.to_string_lossy().to_string(),
destination.path.to_string_lossy().to_string(),
Some(false),