Files
QuiCommit/src/config/manager.rs
SidneyZhang e822ba1f54 feat(commands):为所有命令添加config_path参数支持,实现自定义配置文件路径
♻️ refactor(config):重构ConfigManager,添加with_path_fresh方法用于初始化新配置
🔧 fix(git):改进跨平台路径处理,增强git仓库检测的鲁棒性
 test(tests):添加全面的集成测试,覆盖所有命令和跨平台场景
2026-02-14 14:28:11 +08:00

477 lines
14 KiB
Rust

use super::{AppConfig, GitProfile, TokenConfig};
use anyhow::{bail, Context, Result};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
/// Configuration manager
pub struct ConfigManager {
config: AppConfig,
config_path: PathBuf,
modified: bool,
}
impl ConfigManager {
/// Create new config manager with default path
pub fn new() -> Result<Self> {
let config_path = AppConfig::default_path()?;
Self::with_path(&config_path)
}
/// Create config manager with specific path
pub fn with_path(path: &Path) -> Result<Self> {
let config = if path.exists() {
AppConfig::load(path)?
} else {
AppConfig::default()
};
Ok(Self {
config,
config_path: path.to_path_buf(),
modified: false,
})
}
/// Create config manager with fresh config (ignoring existing)
pub fn with_path_fresh(path: &Path) -> Result<Self> {
Ok(Self {
config: AppConfig::default(),
config_path: path.to_path_buf(),
modified: true,
})
}
/// Get configuration reference
pub fn config(&self) -> &AppConfig {
&self.config
}
/// Get mutable configuration reference
pub fn config_mut(&mut self) -> &mut AppConfig {
self.modified = true;
&mut self.config
}
/// Save configuration if modified
pub fn save(&mut self) -> Result<()> {
if self.modified {
self.config.save(&self.config_path)?;
self.modified = false;
}
Ok(())
}
/// Force save configuration
pub fn force_save(&self) -> Result<()> {
self.config.save(&self.config_path)
}
/// Get configuration file path
pub fn path(&self) -> &Path {
&self.config_path
}
// Profile management
/// Add a new profile
pub fn add_profile(&mut self, name: String, profile: GitProfile) -> Result<()> {
if self.config.profiles.contains_key(&name) {
bail!("Profile '{}' already exists", name);
}
self.config.profiles.insert(name, profile);
self.modified = true;
Ok(())
}
/// Remove a profile
pub fn remove_profile(&mut self, name: &str) -> Result<()> {
if !self.config.profiles.contains_key(name) {
bail!("Profile '{}' does not exist", name);
}
if self.config.default_profile.as_ref() == Some(&name.to_string()) {
self.config.default_profile = None;
}
self.config.repo_profiles.retain(|_, v| v != name);
self.config.profiles.remove(name);
self.modified = true;
Ok(())
}
/// Update a profile
pub fn update_profile(&mut self, name: &str, profile: GitProfile) -> Result<()> {
if !self.config.profiles.contains_key(name) {
bail!("Profile '{}' does not exist", name);
}
self.config.profiles.insert(name.to_string(), profile);
self.modified = true;
Ok(())
}
/// Get a profile
pub fn get_profile(&self, name: &str) -> Option<&GitProfile> {
self.config.profiles.get(name)
}
/// Get mutable profile
pub fn get_profile_mut(&mut self, name: &str) -> Option<&mut GitProfile> {
self.modified = true;
self.config.profiles.get_mut(name)
}
/// List all profile names
pub fn list_profiles(&self) -> Vec<&String> {
self.config.profiles.keys().collect()
}
/// Check if profile exists
pub fn has_profile(&self, name: &str) -> bool {
self.config.profiles.contains_key(name)
}
/// Set default profile
pub fn set_default_profile(&mut self, name: Option<String>) -> Result<()> {
if let Some(ref n) = name {
if !self.config.profiles.contains_key(n) {
bail!("Profile '{}' does not exist", n);
}
}
self.config.default_profile = name;
self.modified = true;
Ok(())
}
/// Get default profile
pub fn default_profile(&self) -> Option<&GitProfile> {
self.config
.default_profile
.as_ref()
.and_then(|name| self.config.profiles.get(name))
}
/// Get default profile name
pub fn default_profile_name(&self) -> Option<&String> {
self.config.default_profile.as_ref()
}
/// Record profile usage
pub fn record_profile_usage(&mut self, name: &str, repo_path: Option<String>) -> Result<()> {
if let Some(profile) = self.config.profiles.get_mut(name) {
profile.record_usage(repo_path);
self.modified = true;
Ok(())
} else {
bail!("Profile '{}' does not exist", name);
}
}
/// Get profile usage statistics
pub fn get_profile_usage(&self, name: &str) -> Option<&super::UsageStats> {
self.config.profiles.get(name).map(|p| &p.usage)
}
// Token management
/// Add a token to a profile
pub fn add_token_to_profile(&mut self, profile_name: &str, service: String, token: TokenConfig) -> Result<()> {
if let Some(profile) = self.config.profiles.get_mut(profile_name) {
profile.add_token(service, token);
self.modified = true;
Ok(())
} else {
bail!("Profile '{}' does not exist", profile_name);
}
}
/// Get a token from a profile
pub fn get_token_from_profile(&self, profile_name: &str, service: &str) -> Option<&TokenConfig> {
self.config.profiles.get(profile_name)?.get_token(service)
}
/// Remove a token from a profile
pub fn remove_token_from_profile(&mut self, profile_name: &str, service: &str) -> Result<()> {
if let Some(profile) = self.config.profiles.get_mut(profile_name) {
profile.remove_token(service);
self.modified = true;
Ok(())
} else {
bail!("Profile '{}' does not exist", profile_name);
}
}
/// List all tokens in a profile
pub fn list_profile_tokens(&self, profile_name: &str) -> Option<Vec<&String>> {
self.config.profiles.get(profile_name).map(|p| p.tokens.keys().collect())
}
// Repository profile management
/// Get profile for repository
pub fn get_repo_profile(&self, repo_path: &str) -> Option<&GitProfile> {
self.config
.repo_profiles
.get(repo_path)
.and_then(|name| self.config.profiles.get(name))
}
/// Set profile for repository
pub fn set_repo_profile(&mut self, repo_path: String, profile_name: String) -> Result<()> {
if !self.config.profiles.contains_key(&profile_name) {
bail!("Profile '{}' does not exist", profile_name);
}
self.config.repo_profiles.insert(repo_path, profile_name);
self.modified = true;
Ok(())
}
/// Remove repository profile mapping
pub fn remove_repo_profile(&mut self, repo_path: &str) {
self.config.repo_profiles.remove(repo_path);
self.modified = true;
}
/// List repository profile mappings
pub fn list_repo_profiles(&self) -> &HashMap<String, String> {
&self.config.repo_profiles
}
/// Get effective profile for a repository (repo-specific -> default)
pub fn get_effective_profile(&self, repo_path: Option<&str>) -> Option<&GitProfile> {
if let Some(path) = repo_path {
if let Some(profile) = self.get_repo_profile(path) {
return Some(profile);
}
}
self.default_profile()
}
/// Check and compare profile with git configuration
pub fn check_profile_config(&self, profile_name: &str, repo: &git2::Repository) -> Result<super::ProfileComparison> {
let profile = self.get_profile(profile_name)
.ok_or_else(|| anyhow::anyhow!("Profile '{}' not found", profile_name))?;
profile.compare_with_git_config(repo)
}
// LLM configuration
/// Get LLM provider
pub fn llm_provider(&self) -> &str {
&self.config.llm.provider
}
/// Set LLM provider
pub fn set_llm_provider(&mut self, provider: String) {
self.config.llm.provider = provider;
self.modified = true;
}
/// Get OpenAI API key
pub fn openai_api_key(&self) -> Option<&String> {
self.config.llm.openai.api_key.as_ref()
}
/// Set OpenAI API key
pub fn set_openai_api_key(&mut self, key: String) {
self.config.llm.openai.api_key = Some(key);
self.modified = true;
}
/// Get Anthropic API key
pub fn anthropic_api_key(&self) -> Option<&String> {
self.config.llm.anthropic.api_key.as_ref()
}
/// Set Anthropic API key
pub fn set_anthropic_api_key(&mut self, key: String) {
self.config.llm.anthropic.api_key = Some(key);
self.modified = true;
}
/// Get Kimi API key
pub fn kimi_api_key(&self) -> Option<&String> {
self.config.llm.kimi.api_key.as_ref()
}
/// Set Kimi API key
pub fn set_kimi_api_key(&mut self, key: String) {
self.config.llm.kimi.api_key = Some(key);
self.modified = true;
}
/// Get Kimi base URL
pub fn kimi_base_url(&self) -> &str {
&self.config.llm.kimi.base_url
}
/// Set Kimi base URL
pub fn set_kimi_base_url(&mut self, url: String) {
self.config.llm.kimi.base_url = url;
self.modified = true;
}
/// Get DeepSeek API key
pub fn deepseek_api_key(&self) -> Option<&String> {
self.config.llm.deepseek.api_key.as_ref()
}
/// Set DeepSeek API key
pub fn set_deepseek_api_key(&mut self, key: String) {
self.config.llm.deepseek.api_key = Some(key);
self.modified = true;
}
/// Get DeepSeek base URL
pub fn deepseek_base_url(&self) -> &str {
&self.config.llm.deepseek.base_url
}
/// Set DeepSeek base URL
pub fn set_deepseek_base_url(&mut self, url: String) {
self.config.llm.deepseek.base_url = url;
self.modified = true;
}
/// Get OpenRouter API key
pub fn openrouter_api_key(&self) -> Option<&String> {
self.config.llm.openrouter.api_key.as_ref()
}
/// Set OpenRouter API key
pub fn set_openrouter_api_key(&mut self, key: String) {
self.config.llm.openrouter.api_key = Some(key);
self.modified = true;
}
/// Get OpenRouter base URL
pub fn openrouter_base_url(&self) -> &str {
&self.config.llm.openrouter.base_url
}
/// Set OpenRouter base URL
pub fn set_openrouter_base_url(&mut self, url: String) {
self.config.llm.openrouter.base_url = url;
self.modified = true;
}
// Commit configuration
/// Get commit format
pub fn commit_format(&self) -> super::CommitFormat {
self.config.commit.format
}
/// Set commit format
pub fn set_commit_format(&mut self, format: super::CommitFormat) {
self.config.commit.format = format;
self.modified = true;
}
/// Check if auto-generate is enabled
pub fn auto_generate_commits(&self) -> bool {
self.config.commit.auto_generate
}
/// Set auto-generate commits
pub fn set_auto_generate_commits(&mut self, enabled: bool) {
self.config.commit.auto_generate = enabled;
self.modified = true;
}
// Tag configuration
/// Get version prefix
pub fn version_prefix(&self) -> &str {
&self.config.tag.version_prefix
}
/// Set version prefix
pub fn set_version_prefix(&mut self, prefix: String) {
self.config.tag.version_prefix = prefix;
self.modified = true;
}
// Changelog configuration
/// Get changelog path
pub fn changelog_path(&self) -> &str {
&self.config.changelog.path
}
/// Set changelog path
pub fn set_changelog_path(&mut self, path: String) {
self.config.changelog.path = path;
self.modified = true;
}
// Language configuration
/// Get output language
pub fn output_language(&self) -> &str {
&self.config.language.output_language
}
/// Set output language
pub fn set_output_language(&mut self, language: String) {
self.config.language.output_language = language;
self.modified = true;
}
/// Get language enum from config
pub fn get_language(&self) -> Option<super::Language> {
super::Language::from_str(&self.config.language.output_language)
}
/// Check if commit types should be kept in English
pub fn keep_types_english(&self) -> bool {
self.config.language.keep_types_english
}
/// Set keep types English flag
pub fn set_keep_types_english(&mut self, keep: bool) {
self.config.language.keep_types_english = keep;
self.modified = true;
}
/// Check if changelog types should be kept in English
pub fn keep_changelog_types_english(&self) -> bool {
self.config.language.keep_changelog_types_english
}
/// Set keep changelog types English flag
pub fn set_keep_changelog_types_english(&mut self, keep: bool) {
self.config.language.keep_changelog_types_english = keep;
self.modified = true;
}
/// Export configuration to TOML string
pub fn export(&self) -> Result<String> {
toml::to_string_pretty(&self.config)
.context("Failed to serialize config")
}
/// Import configuration from TOML string
pub fn import(&mut self, toml_str: &str) -> Result<()> {
self.config = toml::from_str(toml_str)
.context("Failed to parse config")?;
self.modified = true;
Ok(())
}
/// Reset to default configuration
pub fn reset(&mut self) {
self.config = AppConfig::default();
self.modified = true;
}
}
impl Default for ConfigManager {
fn default() -> Self {
Self {
config: AppConfig::default(),
config_path: PathBuf::new(),
modified: false,
}
}
}