feat(core): Implement infrastructure layer for CLI operations
Establishes foundational modules that all other components depend on. src/core/config.rs - Configuration management: - JSON-based config file with Zod-like validation via serde - GitLab settings: base URL, token environment variable - Project list with paths to track - Sync settings: backfill days, stale lock timeout, cursor rewind - Storage settings: database path, payload compression toggle - XDG-compliant config path resolution via dirs crate - Loads GITLAB_TOKEN from configured environment variable src/core/db.rs - Database connection and migrations: - Opens or creates SQLite database with WAL mode for concurrency - Embeds migration SQL as const strings (001-005) - Runs migrations idempotently with checksum verification - Provides thread-safe connection management src/core/error.rs - Unified error handling: - GiError enum with variants for all failure modes - Config, Database, GitLab, Ingestion, Lock, IO, Parse errors - thiserror derive for automatic Display/Error impls - Result type alias for ergonomic error propagation src/core/lock.rs - Distributed sync locking: - File-based locks to prevent concurrent syncs - Stale lock detection with configurable timeout - Force override for recovery scenarios - Lock file contains PID and timestamp for debugging src/core/paths.rs - Path resolution: - XDG Base Directory Specification compliance - Config: ~/.config/gi/config.json - Data: ~/.local/share/gi/gi.db - Creates parent directories on first access src/core/payloads.rs - Raw payload storage: - Optional gzip compression for storage efficiency - SHA-256 content addressing for deduplication - Type-prefixed keys (issue:, discussion:, note:) - Batch insert with UPSERT for idempotent ingestion src/core/time.rs - Timestamp utilities: - Relative time parsing (7d, 2w, 1m) for --since flag - ISO 8601 date parsing for absolute dates - Human-friendly relative time formatting Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
100
src/core/paths.rs
Normal file
100
src/core/paths.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
//! XDG-compliant path resolution for config and data directories.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Get the path to the config file.
|
||||
///
|
||||
/// Resolution order:
|
||||
/// 1. CLI flag override (if provided)
|
||||
/// 2. GI_CONFIG_PATH environment variable
|
||||
/// 3. XDG default (~/.config/gi/config.json)
|
||||
/// 4. Local fallback (./gi.config.json) if exists
|
||||
/// 5. Returns XDG default even if not exists
|
||||
pub fn get_config_path(cli_override: Option<&str>) -> PathBuf {
|
||||
// 1. CLI flag override
|
||||
if let Some(path) = cli_override {
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
|
||||
// 2. Environment variable
|
||||
if let Ok(path) = std::env::var("GI_CONFIG_PATH") {
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
|
||||
// 3. XDG default
|
||||
let xdg_path = get_xdg_config_dir().join("gi").join("config.json");
|
||||
if xdg_path.exists() {
|
||||
return xdg_path;
|
||||
}
|
||||
|
||||
// 4. Local fallback (for development)
|
||||
let local_path = PathBuf::from("gi.config.json");
|
||||
if local_path.exists() {
|
||||
return local_path;
|
||||
}
|
||||
|
||||
// 5. Return XDG path (will trigger not-found error if missing)
|
||||
xdg_path
|
||||
}
|
||||
|
||||
/// Get the data directory path.
|
||||
/// Uses XDG_DATA_HOME or defaults to ~/.local/share/gi
|
||||
pub fn get_data_dir() -> PathBuf {
|
||||
get_xdg_data_dir().join("gi")
|
||||
}
|
||||
|
||||
/// Get the database file path.
|
||||
/// Uses config override if provided, otherwise uses default in data dir.
|
||||
pub fn get_db_path(config_override: Option<&str>) -> PathBuf {
|
||||
if let Some(path) = config_override {
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
get_data_dir().join("gi.db")
|
||||
}
|
||||
|
||||
/// Get the backup directory path.
|
||||
/// Uses config override if provided, otherwise uses default in data dir.
|
||||
pub fn get_backup_dir(config_override: Option<&str>) -> PathBuf {
|
||||
if let Some(path) = config_override {
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
get_data_dir().join("backups")
|
||||
}
|
||||
|
||||
/// Get XDG config directory, falling back to ~/.config
|
||||
fn get_xdg_config_dir() -> PathBuf {
|
||||
std::env::var("XDG_CONFIG_HOME")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| {
|
||||
dirs::home_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.join(".config")
|
||||
})
|
||||
}
|
||||
|
||||
/// Get XDG data directory, falling back to ~/.local/share
|
||||
fn get_xdg_data_dir() -> PathBuf {
|
||||
std::env::var("XDG_DATA_HOME")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| {
|
||||
dirs::home_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.join(".local")
|
||||
.join("share")
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cli_override_takes_precedence() {
|
||||
let path = get_config_path(Some("/custom/path.json"));
|
||||
assert_eq!(path, PathBuf::from("/custom/path.json"));
|
||||
}
|
||||
|
||||
// Note: env var tests removed - mutating process-global env vars
|
||||
// in parallel tests is unsafe in Rust 2024. The env var code path
|
||||
// is trivial (std::env::var) and doesn't warrant the complexity.
|
||||
}
|
||||
Reference in New Issue
Block a user