diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3580232..b82e4c3 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -6,10 +6,15 @@ use std::collections::HashSet; use std::env; use std::fs; use std::io::Cursor; +#[cfg(target_os = "windows")] +use std::os::windows::process::CommandExt; use std::path::Path; use std::path::PathBuf; use std::process::Command; +#[cfg(target_os = "windows")] +const CREATE_NO_WINDOW: u32 = 0x08000000; + #[derive(Serialize)] struct GitCommandResult { command: String, @@ -163,6 +168,13 @@ fn resolve_git_binary(git_path: Option) -> String { .to_string() } +fn new_background_command(program: &str) -> Command { + let mut command = Command::new(program); + #[cfg(target_os = "windows")] + command.creation_flags(CREATE_NO_WINDOW); + command +} + fn run_git_command( repo_path: Option<&str>, git_path: Option, @@ -170,7 +182,7 @@ fn run_git_command( ) -> Result { // Central command executor keeps Git command behavior consistent. let git_binary = resolve_git_binary(git_path); - let mut command = Command::new(&git_binary); + let mut command = new_background_command(&git_binary); if let Some(path) = repo_path { if !Path::new(path).exists() { @@ -203,7 +215,8 @@ fn run_git_output( return Err(format!("Repository path does not exist: {path}")); } - let output = Command::new(&git_binary) + let mut command = new_background_command(&git_binary); + let output = command .current_dir(path) .args(&args) .output() @@ -246,6 +259,48 @@ fn validate_repo_dir(repo_path: &str) -> Result { Ok(path.to_string()) } +#[cfg(target_os = "windows")] +fn normalize_windows_verbatim_path(path: &str) -> String { + if let Some(stripped) = path.strip_prefix(r"\\?\UNC\") { + format!(r"\\{stripped}") + } else if let Some(stripped) = path.strip_prefix(r"\\?\") { + stripped.to_string() + } else { + path.to_string() + } +} + +fn user_facing_path(path: &Path) -> String { + let path = path.to_string_lossy().to_string(); + + #[cfg(target_os = "windows")] + { + normalize_windows_verbatim_path(&path) + } + + #[cfg(not(target_os = "windows"))] + { + path + } +} + +fn repo_display_path(path: &Path) -> String { + let display_path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); + user_facing_path(&display_path) +} + +fn normalize_external_repo_path(path: &str) -> String { + #[cfg(target_os = "windows")] + { + normalize_windows_verbatim_path(path) + } + + #[cfg(not(target_os = "windows"))] + { + path.to_string() + } +} + fn validate_git_path(path: &str) -> Result { let normalized = normalize_repo_path(path)?; if normalized.is_empty() { @@ -870,7 +925,7 @@ fn normalize_git_remote_url(value: &str) -> String { fn local_remote_urls(repo_path: &Path, git_path: Option) -> Vec { let git_binary = resolve_git_binary(git_path); - let Ok(output) = Command::new(&git_binary) + let Ok(output) = new_background_command(&git_binary) .current_dir(repo_path) .args(["config", "--get-regexp", r"^remote\..*\.url$"]) .output() @@ -915,8 +970,7 @@ fn collect_repo_candidates( if is_git_repo(root) { if seen.insert(dedupe_key(root)) { - let display_path = root.canonicalize().unwrap_or_else(|_| root.to_path_buf()); - let path = display_path.to_string_lossy().to_string(); + let path = repo_display_path(root); let Some(matched_remote_url) = matched_server_remote(root, git_path.clone(), allowed_remote_urls) else { @@ -1534,6 +1588,7 @@ fn commit_detail( #[tauri::command] fn open_in_file_explorer(repo_path: String) -> Result<(), String> { let repo_path = validate_repo_dir(&repo_path)?; + let repo_path = normalize_external_repo_path(&repo_path); #[cfg(target_os = "windows")] let mut command = { let mut cmd = Command::new("explorer"); @@ -1562,6 +1617,7 @@ fn open_in_file_explorer(repo_path: String) -> Result<(), String> { #[tauri::command] fn open_in_external_editor(repo_path: String, editor_path: String) -> Result<(), String> { let repo_path = validate_repo_dir(&repo_path)?; + let repo_path = normalize_external_repo_path(&repo_path); let editor_path = editor_path.trim(); if editor_path.is_empty() { return Err("Configure an external editor command in Settings first.".to_string());