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.
70 lines
2.8 KiB
Rust
70 lines
2.8 KiB
Rust
//! Single source of truth for "is this path a git repository?" detection.
|
|
//!
|
|
//! The check is deliberately strict: the exact path must contain a `.git`
|
|
//! entry (directory for regular repos, file for linked worktrees and
|
|
//! submodules). We do **not** walk up to ancestors.
|
|
//!
|
|
//! Rationale: codeg scopes every workspace-facing feature (file tree
|
|
//! watcher, git changes panel, log panel) to the directory the user opens.
|
|
//! If one code path walks up and another doesn't, the UI falls into a
|
|
//! "schizophrenic" state where some panels see a repo and others don't.
|
|
//! Keeping the primitive strict forces every consumer onto the same
|
|
//! interpretation.
|
|
//!
|
|
//! Bare repositories are intentionally not supported — they have no working
|
|
//! tree, which makes them an unusual target for a workspace-oriented editor.
|
|
|
|
use std::{fs, io::ErrorKind, path::Path};
|
|
|
|
use crate::app_error::AppCommandError;
|
|
|
|
/// Returns true when `path` is the root of a git working tree.
|
|
///
|
|
/// `.git` may be a directory (normal repo) or a file (worktree/submodule
|
|
/// pointer). `Path::exists` treats both as present.
|
|
pub fn is_git_repo(path: &Path) -> bool {
|
|
path.join(".git").exists()
|
|
}
|
|
|
|
/// Preflight guard for git commands. Short-circuits with a typed error code
|
|
/// 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> {
|
|
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()))),
|
|
},
|
|
}
|
|
}
|