use crate::error::{GitMultiError, Result};
use git2::Repository;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::{self};
const CONFIG_DIR: &str = ".gitmulti";
const CONFIG_FILE: &str = "config.toml";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoteConfig {
pub url: String,
#[serde(default)]
pub push_url: Option<String>,
#[serde(default)]
pub fetch_refspecs: Vec<String>,
#[serde(default)]
pub push_refspecs: Vec<String>,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub is_primary: bool,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Config {
#[serde(default)]
pub remotes: HashMap<String, RemoteConfig>,
#[serde(default)]
pub default_remote: Option<String>,
#[serde(default)]
pub sync_preferences: SyncPreferences,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SyncPreferences {
#[serde(default = "default_sync_strategy")]
pub default_strategy: String,
#[serde(default)]
pub auto_fetch: bool,
#[serde(default)]
pub auto_push: bool,
}
impl Default for SyncPreferences {
fn default() -> Self {
Self {
default_strategy: default_sync_strategy(),
auto_fetch: true,
auto_push: false,
}
}
}
fn default_sync_strategy() -> String {
"cherry-pick".to_string()
}
impl Config {
pub fn load(repo: &Repository) -> Result<Self> {
let config_path = Self::get_config_path(repo)?;
if !config_path.exists() {
return Ok(Self::default());
}
let config_content = fs::read_to_string(&config_path)?;
let config: Config = toml::from_str(&config_content)
.map_err(GitMultiError::TomlDeserializeError)?;
Ok(config)
}
pub fn save(&self, repo: &Repository) -> Result<()> {
let config_path = Self::get_config_path(repo)?;
if let Some(dir) = config_path.parent() {
fs::create_dir_all(dir)?;
}
let config_content = toml::to_string(self)
.map_err(GitMultiError::TomlSerializeError)?;
fs::write(&config_path, config_content)?;
Ok(())
}
pub fn get_config_path(repo: &Repository) -> Result<std::path::PathBuf> {
let repo_path = repo.path();
Ok(repo_path.join(CONFIG_DIR).join(CONFIG_FILE))
}
pub fn add_remote(&mut self, name: String, url: String) -> Result<()> {
if self.remotes.contains_key(&name) {
return Err(GitMultiError::RemoteAlreadyExists(name));
}
let config = RemoteConfig {
url,
push_url: None,
fetch_refspecs: vec!["+refs/heads/*:refs/remotes/{}/*".to_string()],
push_refspecs: vec!["refs/heads/*:refs/heads/*".to_string()],
tags: vec![".*".to_string()],
is_primary: self.remotes.is_empty(),
};
self.remotes.insert(name.clone(), config);
if self.default_remote.is_none() {
self.default_remote = Some(name);
}
Ok(())
}
pub fn remove_remote(&mut self, name: &str) -> Result<()> {
if !self.remotes.contains_key(name) {
return Err(GitMultiError::RemoteNotFound(name.to_string()));
}
self.remotes.remove(name);
if self.default_remote.as_deref() == Some(name) {
self.default_remote = self.remotes.keys().next().cloned();
}
Ok(())
}
pub fn get_remote(&self, name: &str) -> Result<&RemoteConfig> {
self.remotes.get(name)
.ok_or_else(|| GitMultiError::RemoteNotFound(name.to_string()))
}
pub fn get_remote_names(&self) -> Vec<&String> {
self.remotes.keys().collect()
}
pub fn set_default_remote(&mut self, name: String) -> Result<()> {
if !self.remotes.contains_key(&name) {
return Err(GitMultiError::RemoteNotFound(name));
}
self.default_remote = Some(name);
Ok(())
}
pub fn get_default_remote(&self) -> Option<&String> {
self.default_remote.as_ref()
}
pub fn set_primary_remote(&mut self, name: &str) -> Result<()> {
if !self.remotes.contains_key(name) {
return Err(GitMultiError::RemoteNotFound(name.to_string()));
}
for config in self.remotes.values_mut() {
config.is_primary = false;
}
if let Some(config) = self.remotes.get_mut(name) {
config.is_primary = true;
}
Ok(())
}
pub fn get_primary_remote(&self) -> Option<(&String, &RemoteConfig)> {
self.remotes.iter()
.find(|(_, config)| config.is_primary)
}
}
pub fn init_config(repo: &Repository) -> Result<Config> {
let config = Config::default();
config.save(repo)?;
Ok(config)
}