继续多语言处理

This commit is contained in:
xintaofei
2026-03-07 15:49:00 +08:00
parent 931f69c421
commit 6e5219cc10
18 changed files with 466 additions and 234 deletions

View File

@@ -8,6 +8,8 @@ use crate::db::error::DbError;
pub enum AppErrorCode {
Unknown,
InvalidInput,
ConfigurationMissing,
ConfigurationInvalid,
NotFound,
AlreadyExists,
PermissionDenied,
@@ -81,3 +83,15 @@ impl From<DbError> for AppCommandError {
Self::db(value)
}
}
impl From<String> for AppCommandError {
fn from(value: String) -> Self {
Self::new(AppErrorCode::Unknown, "Operation failed").with_detail(value)
}
}
impl From<&str> for AppCommandError {
fn from(value: &str) -> Self {
Self::from(value.to_string())
}
}

View File

@@ -148,13 +148,18 @@ fn parse_count_from_output(stdout: &[u8]) -> Option<usize> {
String::from_utf8_lossy(stdout).trim().parse::<usize>().ok()
}
async fn get_head_hash(path: &str) -> Result<Option<String>, String> {
fn git_command_error(operation: &str, stderr: &[u8]) -> AppCommandError {
let stderr = String::from_utf8_lossy(stderr).trim().to_string();
AppCommandError::external_command(format!("git {operation} failed"), stderr)
}
async fn get_head_hash(path: &str) -> Result<Option<String>, AppCommandError> {
let output = crate::process::tokio_command("git")
.args(["rev-parse", "HEAD"])
.current_dir(path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
return Ok(None);
@@ -167,17 +172,16 @@ async fn get_head_hash(path: &str) -> Result<Option<String>, String> {
Ok(Some(head))
}
async fn count_files_in_commit(path: &str, commit: &str) -> Result<usize, String> {
async fn count_files_in_commit(path: &str, commit: &str) -> Result<usize, AppCommandError> {
let output = crate::process::tokio_command("git")
.args(["show", "--name-only", "--pretty=format:", commit])
.current_dir(path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git show failed: {}", stderr.trim()));
return Err(git_command_error("show", &output.stderr));
}
Ok(count_non_empty_lines(&String::from_utf8_lossy(
@@ -185,18 +189,21 @@ async fn count_files_in_commit(path: &str, commit: &str) -> Result<usize, String
)))
}
async fn count_changed_files_between(path: &str, base: &str, head: &str) -> Result<usize, String> {
async fn count_changed_files_between(
path: &str,
base: &str,
head: &str,
) -> Result<usize, AppCommandError> {
let range = format!("{}..{}", base, head);
let output = crate::process::tokio_command("git")
.args(["diff", "--name-only", &range])
.current_dir(path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git diff failed: {}", stderr.trim()));
return Err(git_command_error("diff", &output.stderr));
}
Ok(count_non_empty_lines(&String::from_utf8_lossy(
@@ -337,8 +344,8 @@ pub async fn save_folder_opened_conversations(
}
#[tauri::command]
pub async fn create_folder_directory(path: String) -> Result<(), String> {
std::fs::create_dir_all(&path).map_err(|e| e.to_string())
pub async fn create_folder_directory(path: String) -> Result<(), AppCommandError> {
std::fs::create_dir_all(&path).map_err(AppCommandError::io)
}
#[tauri::command]
@@ -427,13 +434,13 @@ fn classify_git_clone_error(stderr: &str) -> AppCommandError {
}
#[tauri::command]
pub async fn get_git_branch(path: String) -> Result<Option<String>, String> {
pub async fn get_git_branch(path: String) -> Result<Option<String>, AppCommandError> {
let output = crate::process::tokio_command("git")
.args(["rev-parse", "--abbrev-ref", "HEAD"])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
return Ok(None);
@@ -447,23 +454,22 @@ pub async fn get_git_branch(path: String) -> Result<Option<String>, String> {
}
#[tauri::command]
pub async fn git_init(path: String) -> Result<(), String> {
pub async fn git_init(path: String) -> Result<(), AppCommandError> {
let output = crate::process::tokio_command("git")
.args(["init"])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git init failed: {}", stderr.trim()));
return Err(git_command_error("init", &output.stderr));
}
Ok(())
}
#[tauri::command]
pub async fn git_pull(path: String) -> Result<GitPullResult, String> {
pub async fn git_pull(path: String) -> Result<GitPullResult, AppCommandError> {
let head_before = get_head_hash(&path).await?;
let output = crate::process::tokio_command("git")
@@ -471,11 +477,10 @@ pub async fn git_pull(path: String) -> Result<GitPullResult, String> {
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git pull failed: {}", stderr.trim()));
return Err(git_command_error("pull", &output.stderr));
}
let head_after = get_head_hash(&path).await?;
@@ -491,23 +496,22 @@ pub async fn git_pull(path: String) -> Result<GitPullResult, String> {
}
#[tauri::command]
pub async fn git_fetch(path: String) -> Result<String, String> {
pub async fn git_fetch(path: String) -> Result<String, AppCommandError> {
let output = crate::process::tokio_command("git")
.args(["fetch", "--all"])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git fetch failed: {}", stderr.trim()));
return Err(git_command_error("fetch --all", &output.stderr));
}
Ok(String::from_utf8_lossy(&output.stderr).trim().to_string())
}
#[tauri::command]
pub async fn git_push(path: String) -> Result<GitPushResult, String> {
pub async fn git_push(path: String) -> Result<GitPushResult, AppCommandError> {
let pushed_commits = estimate_push_commit_count(&path).await;
// Check if the current branch has an upstream configured
@@ -516,7 +520,7 @@ pub async fn git_push(path: String) -> Result<GitPushResult, String> {
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
let has_upstream = upstream_check.status.success();
@@ -527,7 +531,7 @@ pub async fn git_push(path: String) -> Result<GitPushResult, String> {
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
let branch = String::from_utf8_lossy(&branch_output.stdout)
.trim()
.to_string();
@@ -537,19 +541,18 @@ pub async fn git_push(path: String) -> Result<GitPushResult, String> {
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?
.map_err(AppCommandError::io)?
} else {
crate::process::tokio_command("git")
.args(["push"])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?
.map_err(AppCommandError::io)?
};
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git push failed: {}", stderr.trim()));
return Err(git_command_error("push", &output.stderr));
}
Ok(GitPushResult {
@@ -563,7 +566,7 @@ pub async fn git_new_branch(
path: String,
branch_name: String,
start_point: Option<String>,
) -> Result<(), String> {
) -> Result<(), AppCommandError> {
let mut args = vec!["checkout".to_string(), "-b".to_string(), branch_name];
if let Some(start_point) = start_point {
let trimmed = start_point.trim();
@@ -577,11 +580,10 @@ pub async fn git_new_branch(
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git checkout -b failed: {}", stderr.trim()));
return Err(git_command_error("checkout -b", &output.stderr));
}
Ok(())
}
@@ -591,7 +593,7 @@ pub async fn git_worktree_add(
path: String,
branch_name: String,
worktree_path: String,
) -> Result<(), String> {
) -> Result<(), AppCommandError> {
// 校验分支是否已存在
let check = crate::process::tokio_command("git")
.args([
@@ -602,14 +604,22 @@ pub async fn git_worktree_add(
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if check.status.success() {
return Err(format!("分支 '{}' 已存在", branch_name));
return Err(AppCommandError::new(
AppErrorCode::AlreadyExists,
"Branch already exists",
)
.with_detail(branch_name));
}
// 校验目录是否已存在
if std::path::Path::new(&worktree_path).exists() {
return Err(format!("目录 '{}' 已存在", worktree_path));
return Err(AppCommandError::new(
AppErrorCode::AlreadyExists,
"Worktree directory already exists",
)
.with_detail(worktree_path));
}
// 执行 git worktree add -b <branch> <path>
@@ -618,43 +628,40 @@ pub async fn git_worktree_add(
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git worktree add failed: {}", stderr.trim()));
return Err(git_command_error("worktree add", &output.stderr));
}
Ok(())
}
#[tauri::command]
pub async fn git_checkout(path: String, branch_name: String) -> Result<(), String> {
pub async fn git_checkout(path: String, branch_name: String) -> Result<(), AppCommandError> {
let output = crate::process::tokio_command("git")
.args(["checkout", &branch_name])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git checkout failed: {}", stderr.trim()));
return Err(git_command_error("checkout", &output.stderr));
}
Ok(())
}
#[tauri::command]
pub async fn git_list_branches(path: String) -> Result<Vec<String>, String> {
pub async fn git_list_branches(path: String) -> Result<Vec<String>, AppCommandError> {
let output = crate::process::tokio_command("git")
.args(["branch", "--format=%(refname:short)"])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git branch failed: {}", stderr.trim()));
return Err(git_command_error("branch", &output.stderr));
}
let branches = String::from_utf8_lossy(&output.stdout)
@@ -666,49 +673,46 @@ pub async fn git_list_branches(path: String) -> Result<Vec<String>, String> {
}
#[tauri::command]
pub async fn git_stash(path: String) -> Result<String, String> {
pub async fn git_stash(path: String) -> Result<String, AppCommandError> {
let output = crate::process::tokio_command("git")
.args(["stash"])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git stash failed: {}", stderr.trim()));
return Err(git_command_error("stash", &output.stderr));
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}
#[tauri::command]
pub async fn git_stash_pop(path: String) -> Result<String, String> {
pub async fn git_stash_pop(path: String) -> Result<String, AppCommandError> {
let output = crate::process::tokio_command("git")
.args(["stash", "pop"])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git stash pop failed: {}", stderr.trim()));
return Err(git_command_error("stash pop", &output.stderr));
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}
#[tauri::command]
pub async fn git_status(path: String) -> Result<Vec<GitStatusEntry>, String> {
pub async fn git_status(path: String) -> Result<Vec<GitStatusEntry>, AppCommandError> {
let output = crate::process::tokio_command("git")
.args(["status", "--porcelain=v1", "-uall"])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git status failed: {}", stderr.trim()));
return Err(git_command_error("status", &output.stderr));
}
let entries = String::from_utf8_lossy(&output.stdout)
@@ -724,7 +728,7 @@ pub async fn git_status(path: String) -> Result<Vec<GitStatusEntry>, String> {
}
#[tauri::command]
pub async fn git_is_tracked(path: String, file: String) -> Result<bool, String> {
pub async fn git_is_tracked(path: String, file: String) -> Result<bool, AppCommandError> {
let literal_file = to_git_literal_pathspec(&file);
let output = crate::process::tokio_command("git")
.args(["ls-files", "--error-unmatch", "--"])
@@ -732,13 +736,13 @@ pub async fn git_is_tracked(path: String, file: String) -> Result<bool, String>
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
Ok(output.status.success())
}
#[tauri::command]
pub async fn git_diff(path: String, file: Option<String>) -> Result<String, String> {
pub async fn git_diff(path: String, file: Option<String>) -> Result<String, AppCommandError> {
let literal_file = file.as_deref().map(to_git_literal_pathspec);
let mut args = vec!["diff".to_string(), "HEAD".to_string()];
if let Some(ref f) = literal_file {
@@ -751,7 +755,7 @@ pub async fn git_diff(path: String, file: Option<String>) -> Result<String, Stri
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
// For new repos with no HEAD, fall back to diff --cached
@@ -765,7 +769,7 @@ pub async fn git_diff(path: String, file: Option<String>) -> Result<String, Stri
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
return Ok(String::from_utf8_lossy(&fallback.stdout).to_string());
}
@@ -777,10 +781,13 @@ pub async fn git_diff_with_branch(
path: String,
branch: String,
file: Option<String>,
) -> Result<String, String> {
) -> Result<String, AppCommandError> {
let target_branch = branch.trim();
if target_branch.is_empty() {
return Err("Branch name cannot be empty".to_string());
return Err(AppCommandError::new(
AppErrorCode::InvalidInput,
"Branch name cannot be empty",
));
}
let literal_file = file.as_deref().map(to_git_literal_pathspec);
@@ -799,15 +806,14 @@ pub async fn git_diff_with_branch(
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!(
"git diff {} failed: {}",
target_branch,
stderr.trim()
));
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
return Err(
AppCommandError::external_command("git diff failed", stderr)
.with_detail(format!("branch={target_branch}")),
);
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
@@ -818,7 +824,7 @@ pub async fn git_show_diff(
path: String,
commit: String,
file: Option<String>,
) -> Result<String, String> {
) -> Result<String, AppCommandError> {
let literal_file = file.as_deref().map(to_git_literal_pathspec);
let mut args = vec![
"show".to_string(),
@@ -836,11 +842,10 @@ pub async fn git_show_diff(
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git show failed: {}", stderr.trim()));
return Err(git_command_error("show", &output.stderr));
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
@@ -851,7 +856,7 @@ pub async fn git_show_file(
path: String,
file: String,
ref_name: Option<String>,
) -> Result<String, String> {
) -> Result<String, AppCommandError> {
let git_ref = ref_name.unwrap_or_else(|| "HEAD".to_string());
let file_spec = format!("{}:{}", git_ref, file);
@@ -860,7 +865,7 @@ pub async fn git_show_file(
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
// File doesn't exist at this ref (e.g. new/untracked file) — return empty
@@ -869,7 +874,11 @@ pub async fn git_show_file(
let bytes = &output.stdout;
if bytes.iter().take(2048).any(|b| *b == 0) {
return Err("Binary files are not supported".to_string());
return Err(AppCommandError::new(
AppErrorCode::InvalidInput,
"Binary files are not supported",
)
.with_detail(file_spec));
}
Ok(String::from_utf8_lossy(bytes).to_string())
@@ -882,7 +891,7 @@ pub async fn git_commit(
path: String,
message: String,
files: Vec<String>,
) -> Result<GitCommitResult, String> {
) -> Result<GitCommitResult, AppCommandError> {
// Stage selected files
let mut add_args = vec!["add".to_string(), "--".to_string()];
add_args.extend(files.iter().map(|file| to_git_literal_pathspec(file)));
@@ -892,11 +901,10 @@ pub async fn git_commit(
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !add_output.status.success() {
let stderr = String::from_utf8_lossy(&add_output.stderr);
return Err(format!("git add failed: {}", stderr.trim()));
return Err(git_command_error("add", &add_output.stderr));
}
// Commit
@@ -905,11 +913,10 @@ pub async fn git_commit(
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !commit_output.status.success() {
let stderr = String::from_utf8_lossy(&commit_output.stderr);
return Err(format!("git commit failed: {}", stderr.trim()));
return Err(git_command_error("commit", &commit_output.stderr));
}
let committed_files = count_files_in_commit(&path, "HEAD")
@@ -934,10 +941,13 @@ pub async fn git_commit(
}
#[tauri::command]
pub async fn git_rollback_file(path: String, file: String) -> Result<(), String> {
pub async fn git_rollback_file(path: String, file: String) -> Result<(), AppCommandError> {
let target = file.trim();
if target.is_empty() {
return Err("File path cannot be empty".to_string());
return Err(AppCommandError::new(
AppErrorCode::InvalidInput,
"File path cannot be empty",
));
}
let literal_file = to_git_literal_pathspec(target);
@@ -953,7 +963,7 @@ pub async fn git_rollback_file(path: String, file: String) -> Result<(), String>
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if restore_output.status.success() {
return Ok(());
@@ -969,7 +979,10 @@ pub async fn git_rollback_file(path: String, file: String) -> Result<(), String>
&& !restore_stderr_lower.contains("did you mean");
if supports_restore {
return Err(format!("git restore failed: {}", restore_stderr));
return Err(AppCommandError::external_command(
"git restore failed",
restore_stderr,
));
}
let _ = crate::process::tokio_command("git")
@@ -977,25 +990,24 @@ pub async fn git_rollback_file(path: String, file: String) -> Result<(), String>
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
let checkout_output = crate::process::tokio_command("git")
.args(["checkout", "--", &literal_file])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !checkout_output.status.success() {
let checkout_stderr = String::from_utf8_lossy(&checkout_output.stderr);
return Err(format!("git checkout failed: {}", checkout_stderr.trim()));
return Err(git_command_error("checkout --", &checkout_output.stderr));
}
Ok(())
}
#[tauri::command]
pub async fn git_add_files(path: String, files: Vec<String>) -> Result<(), String> {
pub async fn git_add_files(path: String, files: Vec<String>) -> Result<(), AppCommandError> {
if files.is_empty() {
return Ok(());
}
@@ -1008,18 +1020,17 @@ pub async fn git_add_files(path: String, files: Vec<String>) -> Result<(), Strin
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git add failed: {}", stderr.trim()));
return Err(git_command_error("add", &output.stderr));
}
Ok(())
}
#[tauri::command]
pub async fn git_list_all_branches(path: String) -> Result<GitBranchList, String> {
pub async fn git_list_all_branches(path: String) -> Result<GitBranchList, AppCommandError> {
let local_fut = crate::process::tokio_command("git")
.args(["branch", "--format=%(refname:short)"])
.current_dir(&path)
@@ -1037,10 +1048,9 @@ pub async fn git_list_all_branches(path: String) -> Result<GitBranchList, String
let (local_output, remote_output, wt_output) = tokio::join!(local_fut, remote_fut, wt_fut);
let local_output = local_output.map_err(|e| e.to_string())?;
let local_output = local_output.map_err(AppCommandError::io)?;
if !local_output.status.success() {
let stderr = String::from_utf8_lossy(&local_output.stderr);
return Err(format!("git branch failed: {}", stderr.trim()));
return Err(git_command_error("branch", &local_output.stderr));
}
let local: Vec<String> = String::from_utf8_lossy(&local_output.stdout)
@@ -1094,14 +1104,14 @@ pub async fn git_list_all_branches(path: String) -> Result<GitBranchList, String
}
#[tauri::command]
pub async fn git_merge(path: String, branch_name: String) -> Result<GitMergeResult, String> {
pub async fn git_merge(path: String, branch_name: String) -> Result<GitMergeResult, AppCommandError> {
// Count commits to be merged before performing merge
let count_output = crate::process::tokio_command("git")
.args(["rev-list", "--count", &format!("HEAD..{}", branch_name)])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
let merged_commits = if count_output.status.success() {
String::from_utf8_lossy(&count_output.stdout)
@@ -1117,27 +1127,25 @@ pub async fn git_merge(path: String, branch_name: String) -> Result<GitMergeResu
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git merge failed: {}", stderr.trim()));
return Err(git_command_error("merge", &output.stderr));
}
Ok(GitMergeResult { merged_commits })
}
#[tauri::command]
pub async fn git_rebase(path: String, branch_name: String) -> Result<String, String> {
pub async fn git_rebase(path: String, branch_name: String) -> Result<String, AppCommandError> {
let output = crate::process::tokio_command("git")
.args(["rebase", &branch_name])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git rebase failed: {}", stderr.trim()));
return Err(git_command_error("rebase", &output.stderr));
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}
@@ -1147,18 +1155,17 @@ pub async fn git_delete_branch(
path: String,
branch_name: String,
force: bool,
) -> Result<String, String> {
) -> Result<String, AppCommandError> {
let flag = if force { "-D" } else { "-d" };
let output = crate::process::tokio_command("git")
.args(["branch", flag, &branch_name])
.current_dir(&path)
.output()
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("git branch {} failed: {}", flag, stderr.trim()));
return Err(git_command_error(&format!("branch {flag}"), &output.stderr));
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}

View File

@@ -34,13 +34,13 @@ fn normalize_proxy_settings(
.filter(|value| !value.is_empty())
.ok_or_else(|| {
AppCommandError::new(
AppErrorCode::InvalidInput,
AppErrorCode::ConfigurationMissing,
"Proxy URL is required when proxy is enabled",
)
})?;
reqwest::Proxy::all(proxy_url).map_err(|e| {
AppCommandError::new(AppErrorCode::InvalidInput, "Invalid proxy URL")
AppCommandError::new(AppErrorCode::ConfigurationInvalid, "Invalid proxy URL")
.with_detail(e.to_string())
})?;
@@ -63,7 +63,7 @@ pub(crate) async fn load_system_proxy_settings(
let parsed = serde_json::from_str::<SystemProxySettings>(&raw).map_err(|e| {
AppCommandError::new(
AppErrorCode::InvalidInput,
AppErrorCode::ConfigurationInvalid,
"Failed to parse stored proxy settings",
)
.with_detail(e.to_string())
@@ -84,7 +84,7 @@ pub(crate) async fn load_system_language_settings(
serde_json::from_str::<SystemLanguageSettings>(&raw).map_err(|e| {
AppCommandError::new(
AppErrorCode::InvalidInput,
AppErrorCode::ConfigurationInvalid,
"Failed to parse stored language settings",
)
.with_detail(e.to_string())
@@ -116,13 +116,7 @@ pub async fn update_system_proxy_settings(
.await
.map_err(AppCommandError::from)?;
proxy::apply_system_proxy_settings(&normalized).map_err(|e| {
AppCommandError::new(
AppErrorCode::ExternalCommandFailed,
"Failed to apply system proxy settings",
)
.with_detail(e)
})?;
proxy::apply_system_proxy_settings(&normalized)?;
Ok(normalized)
}

View File

@@ -1,3 +1,4 @@
use crate::app_error::{AppCommandError, AppErrorCode};
use crate::models::SystemProxySettings;
const PROXY_ENV_KEYS: [&str; 6] = [
@@ -9,14 +10,19 @@ const PROXY_ENV_KEYS: [&str; 6] = [
"all_proxy",
];
pub fn apply_system_proxy_settings(settings: &SystemProxySettings) -> Result<(), String> {
pub fn apply_system_proxy_settings(settings: &SystemProxySettings) -> Result<(), AppCommandError> {
if settings.enabled {
let proxy_url = settings
.proxy_url
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| "proxy url is required when proxy is enabled".to_string())?;
.ok_or_else(|| {
AppCommandError::new(
AppErrorCode::ConfigurationMissing,
"Proxy URL is required when proxy is enabled",
)
})?;
for key in PROXY_ENV_KEYS {
unsafe {