use std::time::Duration;
use git2::Repository;
use crate::error::{ConfigError, Result, StackError};
use crate::stack::{Granularity, StackModel};
pub struct WorkonConfig<'repo> {
repo: &'repo Repository,
}
impl<'repo> WorkonConfig<'repo> {
pub fn new(repo: &'repo Repository) -> Result<Self> {
Ok(Self { repo })
}
pub fn default_branch(&self, cli_override: Option<&str>) -> Result<Option<String>> {
if let Some(override_val) = cli_override {
return Ok(Some(override_val.to_string()));
}
let config = self.repo.config()?;
match config.get_string("workon.defaultBranch") {
Ok(val) => Ok(Some(val)),
Err(_) => Ok(None), }
}
pub fn pr_format(&self, cli_override: Option<&str>) -> Result<String> {
let format = if let Some(override_val) = cli_override {
override_val.to_string()
} else {
let config = self.repo.config()?;
config
.get_string("workon.prFormat")
.unwrap_or_else(|_| "pr-{number}".to_string())
};
if !format.contains("{number}") {
return Err(ConfigError::InvalidPrFormat {
format: format.clone(),
reason: "Format must contain {number} placeholder".to_string(),
}
.into());
}
let valid_placeholders = ["{number}", "{title}", "{author}", "{branch}"];
let mut remaining = format.clone();
for placeholder in &valid_placeholders {
remaining = remaining.replace(placeholder, "");
}
if remaining.contains('{') {
return Err(ConfigError::InvalidPrFormat {
format: format.clone(),
reason: format!(
"Invalid placeholder found. Valid placeholders: {}",
valid_placeholders.join(", ")
),
}
.into());
}
Ok(format)
}
pub fn post_create_hooks(&self) -> Result<Vec<String>> {
self.read_multivar("workon.postCreateHook")
}
pub fn copy_patterns(&self) -> Result<Vec<String>> {
self.read_multivar("workon.copyPattern")
}
pub fn copy_excludes(&self) -> Result<Vec<String>> {
self.read_multivar("workon.copyExclude")
}
pub fn copy_include_ignored(&self, cli_override: Option<bool>) -> Result<bool> {
if let Some(override_val) = cli_override {
return Ok(override_val);
}
let config = self.repo.config()?;
match config.get_bool("workon.copyIncludeIgnored") {
Ok(val) => Ok(val),
Err(_) => Ok(true),
}
}
pub fn auto_copy(&self, cli_override: Option<bool>) -> Result<bool> {
if let Some(override_val) = cli_override {
return Ok(override_val);
}
let config = self.repo.config()?;
match config.get_bool("workon.autoCopy") {
Ok(val) => Ok(val),
Err(_) => Ok(false),
}
}
pub fn prune_protected_branches(&self) -> Result<Vec<String>> {
self.read_multivar("workon.pruneProtectedBranches")
}
pub fn is_protected(&self, branch_name: &str) -> bool {
let patterns = match self.prune_protected_branches() {
Ok(p) => p,
Err(_) => return false,
};
for pattern in patterns {
if pattern == branch_name {
return true;
}
if pattern == "*" {
return true;
}
if let Some(prefix) = pattern.strip_suffix("/*") {
if branch_name.starts_with(&format!("{}/", prefix)) {
return true;
}
}
}
false
}
pub fn hook_timeout(&self) -> Result<Duration> {
let config = self.repo.config()?;
let seconds = match config.get_i64("workon.hookTimeout") {
Ok(val) => val.max(0) as u64,
Err(_) => 300,
};
Ok(Duration::from_secs(seconds))
}
pub fn stack_model(&self, cli_override: Option<&str>) -> Result<StackModel> {
let raw = if let Some(val) = cli_override {
Some(val.to_string())
} else {
let config = self.repo.config()?;
config.get_string("workon.stackModel").ok()
};
match raw.as_deref() {
None | Some("auto") => Ok(StackModel::detect(self.repo)),
Some("none") => Ok(StackModel::None),
Some("graphite") => Ok(StackModel::Graphite),
Some(other) if matches!(other, "branchless" | "sapling" | "spr") => {
Err(StackError::UnsupportedModel {
model: other.to_string(),
}
.into())
}
Some(other) => Err(StackError::UnknownModel {
value: other.to_string(),
}
.into()),
}
}
pub fn stack_worktree_granularity(&self, cli_override: Option<&str>) -> Result<Granularity> {
let raw = if let Some(val) = cli_override {
Some(val.to_string())
} else {
let config = self.repo.config()?;
config.get_string("workon.stackWorktreeGranularity").ok()
};
match raw.as_deref() {
None | Some("stack") => Ok(Granularity::Stack),
Some("diff") => Err(StackError::UnsupportedGranularity.into()),
Some(other) => Err(StackError::UnknownGranularity {
value: other.to_string(),
}
.into()),
}
}
pub fn gt_auto_track(&self, cli_override: Option<bool>) -> Result<bool> {
if let Some(val) = cli_override {
return Ok(val);
}
let config = self.repo.config()?;
match config.get_bool("workon.gtAutoTrack") {
Ok(val) => Ok(val),
Err(_) => Ok(true),
}
}
fn read_multivar(&self, key: &str) -> Result<Vec<String>> {
let config = self.repo.config()?;
let mut values = Vec::new();
if let Ok(mut entries) = config.multivar(key, None) {
while let Some(entry) = entries.next() {
let entry = entry?;
if let Some(value) = entry.value() {
values.push(value.to_string());
}
}
}
Ok(values)
}
}