Consolidate `.git` presence detection into a shared `git_repo` module used by both the workspace state watcher and the command preflight helper, replacing duplicated local definitions. Introduce `AppErrorCode::NotAGitRepository` (HTTP 422) and preflight eleven frontend-callable git commands (log, status, list-branches, diff, diff-with-branch, show-diff, show-file, push-info, list-remotes, list-all-branches, commit-branches) so non-git folders short-circuit with a structured error instead of leaking locale-dependent git stderr. Frontend `isNotAGitRepoError` checks the error code first and falls back to a multi-language regex list centralized in `src/i18n/git-error-patterns.ts`, covering the nine languages git actually translates into. Wire the git log panel to `workspaceState.isGitRepo` rather than a local cached flag, so running `git init` or deleting `.git` externally propagates through the watcher and refreshes the panel automatically.
137 lines
4.0 KiB
Rust
137 lines
4.0 KiB
Rust
use serde::Serialize;
|
|
|
|
use crate::db::error::DbError;
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum AppErrorCode {
|
|
InvalidInput,
|
|
ConfigurationMissing,
|
|
ConfigurationInvalid,
|
|
NotFound,
|
|
NotAGitRepository,
|
|
AlreadyExists,
|
|
PermissionDenied,
|
|
DependencyMissing,
|
|
NetworkError,
|
|
AuthenticationFailed,
|
|
DatabaseError,
|
|
IoError,
|
|
ExternalCommandFailed,
|
|
WindowOperationFailed,
|
|
TaskExecutionFailed,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, thiserror::Error)]
|
|
#[error("{message}")]
|
|
pub struct AppCommandError {
|
|
pub code: AppErrorCode,
|
|
pub message: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub detail: Option<String>,
|
|
}
|
|
|
|
impl AppCommandError {
|
|
pub fn new(code: AppErrorCode, message: impl Into<String>) -> Self {
|
|
Self {
|
|
code,
|
|
message: message.into(),
|
|
detail: None,
|
|
}
|
|
}
|
|
|
|
pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
|
|
self.detail = Some(detail.into());
|
|
self
|
|
}
|
|
|
|
pub fn db(err: DbError) -> Self {
|
|
Self::new(AppErrorCode::DatabaseError, "Database operation failed")
|
|
.with_detail(err.to_string())
|
|
}
|
|
|
|
pub fn invalid_input(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::InvalidInput, message)
|
|
}
|
|
|
|
pub fn configuration_missing(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::ConfigurationMissing, message)
|
|
}
|
|
|
|
pub fn configuration_invalid(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::ConfigurationInvalid, message)
|
|
}
|
|
|
|
pub fn not_found(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::NotFound, message)
|
|
}
|
|
|
|
pub fn not_a_git_repository(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::NotAGitRepository, message)
|
|
}
|
|
|
|
pub fn already_exists(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::AlreadyExists, message)
|
|
}
|
|
|
|
pub fn permission_denied(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::PermissionDenied, message)
|
|
}
|
|
|
|
pub fn dependency_missing(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::DependencyMissing, message)
|
|
}
|
|
|
|
pub fn network(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::NetworkError, message)
|
|
}
|
|
|
|
pub fn authentication_failed(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::AuthenticationFailed, message)
|
|
}
|
|
|
|
pub fn database_error(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::DatabaseError, message)
|
|
}
|
|
|
|
pub fn io_error(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::IoError, message)
|
|
}
|
|
|
|
pub fn task_execution_failed(message: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::TaskExecutionFailed, message)
|
|
}
|
|
|
|
pub fn io(err: std::io::Error) -> Self {
|
|
let code = match err.kind() {
|
|
std::io::ErrorKind::NotFound => AppErrorCode::NotFound,
|
|
std::io::ErrorKind::PermissionDenied => AppErrorCode::PermissionDenied,
|
|
std::io::ErrorKind::AlreadyExists => AppErrorCode::AlreadyExists,
|
|
_ => AppErrorCode::IoError,
|
|
};
|
|
|
|
let message = match code {
|
|
AppErrorCode::NotFound => "Resource not found",
|
|
AppErrorCode::PermissionDenied => "Permission denied",
|
|
AppErrorCode::AlreadyExists => "Resource already exists",
|
|
_ => "I/O operation failed",
|
|
};
|
|
|
|
Self::new(code, message).with_detail(err.to_string())
|
|
}
|
|
|
|
pub fn window(message: impl Into<String>, detail: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::WindowOperationFailed, message).with_detail(detail)
|
|
}
|
|
|
|
pub fn external_command(message: impl Into<String>, detail: impl Into<String>) -> Self {
|
|
Self::new(AppErrorCode::ExternalCommandFailed, message).with_detail(detail)
|
|
}
|
|
}
|
|
|
|
impl From<DbError> for AppCommandError {
|
|
fn from(value: DbError) -> Self {
|
|
Self::db(value)
|
|
}
|
|
}
|