use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct WorktreeConfig {
pub enabled: bool,
pub base_ref: WorktreeBaseRef,
pub default_branch: String,
pub root: String,
pub branch_prefix: String,
pub prune_branch_on_remove: bool,
pub cleanup_on_completion: bool,
pub bg_isolation: BgIsolation,
pub git_timeout_secs: u64,
}
fn default_git_timeout_secs() -> u64 {
30
}
impl Default for WorktreeConfig {
fn default() -> Self {
Self {
enabled: false,
base_ref: WorktreeBaseRef::default(),
default_branch: "main".to_owned(),
root: ".claude/worktrees".to_owned(),
branch_prefix: "agent/".to_owned(),
prune_branch_on_remove: false,
cleanup_on_completion: true,
bg_isolation: BgIsolation::default(),
git_timeout_secs: default_git_timeout_secs(),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum WorktreeBaseRef {
#[default]
Head,
Fresh,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum BgIsolation {
#[default]
Worktree,
None,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn worktree_config_default_values() {
let cfg = WorktreeConfig::default();
assert!(!cfg.enabled);
assert!(matches!(cfg.base_ref, WorktreeBaseRef::Head));
assert_eq!(cfg.default_branch, "main");
assert_eq!(cfg.root, ".claude/worktrees");
assert_eq!(cfg.branch_prefix, "agent/");
assert!(!cfg.prune_branch_on_remove);
assert!(cfg.cleanup_on_completion);
assert_eq!(cfg.bg_isolation, BgIsolation::Worktree);
assert_eq!(cfg.git_timeout_secs, 30);
}
#[test]
fn worktree_config_roundtrip_toml() {
let cfg = WorktreeConfig::default();
let serialized = toml::to_string(&cfg).expect("serialize");
let deserialized: WorktreeConfig = toml::from_str(&serialized).expect("deserialize");
assert!(!deserialized.enabled);
assert_eq!(deserialized.root, cfg.root);
assert_eq!(deserialized.branch_prefix, cfg.branch_prefix);
assert_eq!(deserialized.bg_isolation, cfg.bg_isolation);
assert_eq!(deserialized.git_timeout_secs, 30);
}
#[test]
fn worktree_base_ref_roundtrip_toml() {
#[derive(Serialize, Deserialize, Debug)]
struct Wrapper {
base_ref: WorktreeBaseRef,
}
let head = Wrapper {
base_ref: WorktreeBaseRef::Head,
};
let s = toml::to_string(&head).expect("serialize Head");
assert!(s.contains("head"), "expected 'head' in: {s}");
let rt: Wrapper = toml::from_str(&s).expect("deserialize Head");
assert!(matches!(rt.base_ref, WorktreeBaseRef::Head));
let fresh = Wrapper {
base_ref: WorktreeBaseRef::Fresh,
};
let s = toml::to_string(&fresh).expect("serialize Fresh");
assert!(s.contains("fresh"), "expected 'fresh' in: {s}");
let rt: Wrapper = toml::from_str(&s).expect("deserialize Fresh");
assert!(matches!(rt.base_ref, WorktreeBaseRef::Fresh));
}
#[test]
fn bg_isolation_roundtrip_toml() {
#[derive(Serialize, Deserialize, Debug)]
struct Wrapper {
bg_isolation: BgIsolation,
}
let iso = Wrapper {
bg_isolation: BgIsolation::Worktree,
};
let s = toml::to_string(&iso).expect("serialize Worktree");
assert!(s.contains("worktree"), "expected 'worktree' in: {s}");
let rt: Wrapper = toml::from_str(&s).expect("deserialize Worktree");
assert_eq!(rt.bg_isolation, BgIsolation::Worktree);
let none = Wrapper {
bg_isolation: BgIsolation::None,
};
let s = toml::to_string(&none).expect("serialize None");
assert!(s.contains("none"), "expected 'none' in: {s}");
let rt: Wrapper = toml::from_str(&s).expect("deserialize None");
assert_eq!(rt.bg_isolation, BgIsolation::None);
}
#[test]
fn worktree_config_enabled_roundtrip() {
let toml_src = r#"
enabled = true
base_ref = "fresh"
default_branch = "develop"
root = ".worktrees"
branch_prefix = "bot/"
prune_branch_on_remove = true
cleanup_on_completion = false
bg_isolation = "none"
"#;
let cfg: WorktreeConfig = toml::from_str(toml_src).expect("deserialize custom");
assert!(cfg.enabled);
assert!(matches!(cfg.base_ref, WorktreeBaseRef::Fresh));
assert_eq!(cfg.default_branch, "develop");
assert_eq!(cfg.root, ".worktrees");
assert_eq!(cfg.branch_prefix, "bot/");
assert!(cfg.prune_branch_on_remove);
assert!(!cfg.cleanup_on_completion);
assert_eq!(cfg.bg_isolation, BgIsolation::None);
assert_eq!(cfg.git_timeout_secs, 30);
}
#[test]
fn worktree_config_git_timeout_secs_custom() {
let toml_src = "enabled = true\ngit_timeout_secs = 120\n";
let cfg: WorktreeConfig = toml::from_str(toml_src).expect("deserialize");
assert_eq!(cfg.git_timeout_secs, 120);
}
#[test]
fn worktree_config_git_timeout_secs_defaults_when_absent() {
let toml_src = "enabled = false\n";
let cfg: WorktreeConfig = toml::from_str(toml_src).expect("deserialize");
assert_eq!(cfg.git_timeout_secs, 30);
}
}