use fs2::FileExt;
use crate::config::ConfigError;
use crate::path::format_path_for_display;
use super::UserConfig;
use super::path;
use super::sections::CommitGenerationConfig;
const NO_CONFIG_DIR_MSG: &str = "Cannot determine config directory. Set $HOME or $XDG_CONFIG_HOME";
pub(crate) fn acquire_config_lock(
config_path: &std::path::Path,
) -> Result<std::fs::File, ConfigError> {
let lock_path = config_path.with_extension("toml.lock");
if let Some(parent) = lock_path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| ConfigError(format!("Failed to create config directory: {e}")))?;
}
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&lock_path)
.map_err(|e| ConfigError(format!("Failed to open lock file: {e}")))?;
file.lock_exclusive()
.map_err(|e| ConfigError(format!("Failed to acquire config lock: {e}")))?;
Ok(file)
}
impl UserConfig {
pub(super) fn with_locked_mutation<F>(
&mut self,
config_path: Option<&std::path::Path>,
mutate: F,
) -> Result<(), ConfigError>
where
F: FnOnce(&mut Self) -> bool,
{
let path = match config_path {
Some(p) => p.to_path_buf(),
None => path::config_path().ok_or_else(|| ConfigError(NO_CONFIG_DIR_MSG.into()))?,
};
let _lock = acquire_config_lock(&path)?;
self.reload_from(&path)?;
if mutate(self) {
self.save_to(&path)?;
}
Ok(())
}
fn reload_from(&mut self, path: &std::path::Path) -> Result<(), ConfigError> {
if !path.exists() {
return Ok(());
}
let content = std::fs::read_to_string(path).map_err(|e| {
ConfigError(format!(
"Failed to read config file {}: {}",
format_path_for_display(path),
e
))
})?;
let migrated = crate::config::deprecation::migrate_content(&content);
let disk_config: UserConfig = toml::from_str(&migrated).map_err(|e| {
ConfigError(format!(
"Failed to parse config file {}: {}",
format_path_for_display(path),
e
))
})?;
*self = disk_config;
Ok(())
}
pub fn set_skip_shell_integration_prompt(
&mut self,
config_path: Option<&std::path::Path>,
) -> Result<(), ConfigError> {
self.with_locked_mutation(config_path, |config| {
if config.skip_shell_integration_prompt {
return false;
}
config.skip_shell_integration_prompt = true;
true
})
}
pub fn set_skip_commit_generation_prompt(
&mut self,
config_path: Option<&std::path::Path>,
) -> Result<(), ConfigError> {
self.with_locked_mutation(config_path, |config| {
if config.skip_commit_generation_prompt {
return false;
}
config.skip_commit_generation_prompt = true;
true
})
}
pub fn set_project_worktree_path(
&mut self,
project: &str,
worktree_path: String,
config_path: Option<&std::path::Path>,
) -> Result<(), ConfigError> {
self.with_locked_mutation(config_path, |config| {
let entry = config.projects.entry(project.to_string()).or_default();
if entry.worktree_path.as_ref() == Some(&worktree_path) {
return false;
}
entry.worktree_path = Some(worktree_path);
true
})
}
pub fn set_commit_generation_command(
&mut self,
command: String,
config_path: Option<&std::path::Path>,
) -> Result<(), ConfigError> {
self.with_locked_mutation(config_path, |config| {
let gen_config = config
.commit
.generation
.get_or_insert_with(CommitGenerationConfig::default);
gen_config.command = Some(command.clone());
true
})
}
}