From 95a0c527c4a992be8fddd33409648a42132138b5 Mon Sep 17 00:00:00 2001 From: xintaofei Date: Sat, 18 Apr 2026 23:49:42 +0800 Subject: [PATCH] fix(git): restore non-repo fallback and refine repo preflight errors Reintroduce a local not-a-git-repo fallback state in the git log panel so non-repo errors still render the dedicated empty state when workspace state streaming is degraded. Improve git repository preflight classification by distinguishing missing paths, permission issues, and non-directory targets before checking .git presence. Add not_a_git_repository to the frontend AppErrorCode union for explicit typed handling. --- src-tauri/src/git_repo.rs | 43 ++++++++++++++++--- .../layout/aux-panel-git-log-tab.tsx | 26 +++++++++-- src/lib/types.ts | 1 + 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src-tauri/src/git_repo.rs b/src-tauri/src/git_repo.rs index 34dd1c2..be0eacf 100644 --- a/src-tauri/src/git_repo.rs +++ b/src-tauri/src/git_repo.rs @@ -14,7 +14,7 @@ //! Bare repositories are intentionally not supported — they have no working //! tree, which makes them an unusual target for a workspace-oriented editor. -use std::path::Path; +use std::{fs, io::ErrorKind, path::Path}; use crate::app_error::AppCommandError; @@ -30,11 +30,40 @@ pub fn is_git_repo(path: &Path) -> bool { /// when the target path is not a git working tree, so callers avoid locale- /// dependent stderr parsing for the most common "wrong folder" failure. pub fn ensure_git_repo(path: &str) -> Result<(), AppCommandError> { - if is_git_repo(Path::new(path)) { - Ok(()) - } else { - Err(AppCommandError::not_a_git_repository(format!( - "Not a Git repository: {path}" - ))) + let root = Path::new(path); + + let root_meta = fs::metadata(root).map_err(|err| match err.kind() { + ErrorKind::NotFound => { + AppCommandError::not_found(format!("Workspace path does not exist: {path}")) + } + ErrorKind::PermissionDenied => { + AppCommandError::permission_denied(format!("Cannot access workspace path: {path}")) + .with_detail(err.to_string()) + } + _ => AppCommandError::io(err) + .with_detail(format!("Failed to inspect workspace path: {path}")), + })?; + + if !root_meta.is_dir() { + return Err(AppCommandError::invalid_input(format!( + "Workspace path is not a directory: {path}" + ))); + } + + let git_path = root.join(".git"); + match fs::metadata(&git_path) { + Ok(_) => Ok(()), + Err(err) => match err.kind() { + ErrorKind::NotFound => Err(AppCommandError::not_a_git_repository(format!( + "Not a Git repository: {path}" + ))), + ErrorKind::PermissionDenied => Err(AppCommandError::permission_denied(format!( + "Cannot access Git metadata: {}", + git_path.display() + )) + .with_detail(err.to_string())), + _ => Err(AppCommandError::io(err) + .with_detail(format!("Failed to inspect Git metadata: {}", git_path.display()))), + }, } } diff --git a/src/components/layout/aux-panel-git-log-tab.tsx b/src/components/layout/aux-panel-git-log-tab.tsx index 6f2f442..53caf0e 100644 --- a/src/components/layout/aux-panel-git-log-tab.tsx +++ b/src/components/layout/aux-panel-git-log-tab.tsx @@ -699,6 +699,7 @@ export function GitLogTab() { const [loading, setLoading] = useState(true) const [refreshing, setRefreshing] = useState(false) const [error, setError] = useState(null) + const [notAGitRepo, setNotAGitRepo] = useState(false) const [scrolled, setScrolled] = useState(false) const [openByCommit, setOpenByCommit] = useState>({}) const [branchesByCommit, setBranchesByCommit] = useState< @@ -767,9 +768,9 @@ export function GitLogTab() { // check in `git_list_all_branches` would short-circuit on non-git folders // anyway, but skipping the call saves an unnecessary round trip. useEffect(() => { - if (!isGitRepo) return + if (!isGitRepo || notAGitRepo) return void refreshBranches() - }, [isGitRepo, refreshBranches]) + }, [isGitRepo, notAGitRepo, refreshBranches]) const fetchCommitBranches = useCallback( async (fullHash: string) => { @@ -814,6 +815,7 @@ export function GitLogTab() { setBranchesError({}) } setError(null) + setNotAGitRepo(false) try { const result = await gitLog(folder.path, 100, branch ?? undefined) setEntries(result.entries) @@ -836,6 +838,7 @@ export function GitLogTab() { } } catch (e) { if (isNotAGitRepoError(e)) { + setNotAGitRepo(true) // Workspace state will flip isGitRepo within the next watch flush; // clear entries so stale log data does not linger while we wait. setEntries([]) @@ -853,6 +856,10 @@ export function GitLogTab() { [folder?.path, selectedBranch] ) + useEffect(() => { + setNotAGitRepo(false) + }, [folder?.path]) + const handleRefresh = useCallback(() => { void fetchLog({ inline: true }) }, [fetchLog]) @@ -969,6 +976,7 @@ export function GitLogTab() { // and either re-fetches or clears the log to stay aligned with the other // workspace panels. if (!isGitRepo) { + setNotAGitRepo(false) setEntries([]) setError(null) setLoading(false) @@ -1042,7 +1050,7 @@ export function GitLogTab() { ) } - if (!isGitRepo) { + if (!isGitRepo || notAGitRepo) { return (
@@ -1051,6 +1059,18 @@ export function GitLogTab() {

{t("notAGitRepoHint")}

+ {isGitRepo && ( + + )}
) diff --git a/src/lib/types.ts b/src/lib/types.ts index 25e8973..c127533 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -11,6 +11,7 @@ export type AppErrorCode = | "configuration_missing" | "configuration_invalid" | "not_found" + | "not_a_git_repository" | "already_exists" | "permission_denied" | "dependency_missing"