Skip to content

Commit fc76b6f

Browse files
committed
feat(repo): add repository management operations (add, update, remove)
Add SoarContext methods to programmatically manage repositories in config. Config::save() now uses annotated document to preserve doc comments.
1 parent 61c0efb commit fc76b6f

3 files changed

Lines changed: 112 additions & 2 deletions

File tree

crates/soar-config/src/config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -574,11 +574,11 @@ impl Config {
574574

575575
pub fn save(&self) -> Result<()> {
576576
let config_path = CONFIG_PATH.read().unwrap().to_path_buf();
577-
let serialized = toml::to_string_pretty(self)?;
577+
let annotated_doc = self.to_annotated_document()?;
578578
if let Some(parent) = config_path.parent() {
579579
fs::create_dir_all(parent)?;
580580
}
581-
fs::write(&config_path, serialized)?;
581+
fs::write(&config_path, annotated_doc.to_string())?;
582582
info!("Configuration saved to {}", config_path.display());
583583
Ok(())
584584
}

crates/soar-operations/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod health;
88
pub mod install;
99
pub mod list;
1010
pub mod remove;
11+
pub mod repo;
1112
pub mod run;
1213
pub mod search;
1314
pub mod switch;

crates/soar-operations/src/repo.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use std::fs;
2+
3+
use soar_config::{
4+
config::{Config, CONFIG},
5+
repository::Repository,
6+
};
7+
use soar_core::{error::ErrorContext, SoarResult};
8+
9+
use crate::SoarContext;
10+
11+
/// Fields that can be updated on an existing repository.
12+
pub struct RepoUpdate {
13+
pub url: Option<String>,
14+
pub enabled: Option<bool>,
15+
pub pubkey: Option<String>,
16+
pub desktop_integration: Option<bool>,
17+
pub signature_verification: Option<bool>,
18+
pub sync_interval: Option<String>,
19+
}
20+
21+
/// Loads a fresh config from disk, applies the mutation, validates, saves, and updates the global.
22+
fn modify_config(mutate: impl FnOnce(&mut Config) -> SoarResult<()>) -> SoarResult<()> {
23+
let mut config = Config::new()?;
24+
mutate(&mut config)?;
25+
config.save()?;
26+
27+
let mut global = CONFIG.write()?;
28+
*global = Some(config);
29+
Ok(())
30+
}
31+
32+
impl SoarContext {
33+
/// Add a new repository to the configuration.
34+
pub fn add_repository(&self, repo: Repository) -> SoarResult<()> {
35+
modify_config(|config| {
36+
if config.repositories.iter().any(|r| r.name == repo.name) {
37+
return Err(soar_config::error::ConfigError::DuplicateRepositoryName(
38+
repo.name.clone(),
39+
)
40+
.into());
41+
}
42+
43+
config.repositories.push(repo);
44+
config.resolve()?;
45+
Ok(())
46+
})
47+
}
48+
49+
/// Update an existing repository's configuration.
50+
pub fn update_repository(&self, name: &str, update: RepoUpdate) -> SoarResult<()> {
51+
modify_config(|config| {
52+
let repo = config
53+
.repositories
54+
.iter_mut()
55+
.find(|r| r.name == name)
56+
.ok_or_else(|| {
57+
soar_config::error::ConfigError::InvalidRepository(name.to_string())
58+
})?;
59+
60+
if let Some(url) = update.url {
61+
repo.url = url;
62+
}
63+
if let Some(enabled) = update.enabled {
64+
repo.enabled = Some(enabled);
65+
}
66+
if let Some(pubkey) = update.pubkey {
67+
repo.pubkey = Some(pubkey);
68+
}
69+
if let Some(desktop_integration) = update.desktop_integration {
70+
repo.desktop_integration = Some(desktop_integration);
71+
}
72+
if let Some(signature_verification) = update.signature_verification {
73+
repo.signature_verification = Some(signature_verification);
74+
}
75+
if let Some(sync_interval) = update.sync_interval {
76+
repo.sync_interval = Some(sync_interval);
77+
}
78+
79+
config.resolve()?;
80+
Ok(())
81+
})
82+
}
83+
84+
/// Remove a repository from the configuration and clean up its data.
85+
pub fn remove_repository(&self, name: &str) -> SoarResult<()> {
86+
modify_config(|config| {
87+
let idx = config
88+
.repositories
89+
.iter()
90+
.position(|r| r.name == name)
91+
.ok_or_else(|| {
92+
soar_config::error::ConfigError::InvalidRepository(name.to_string())
93+
})?;
94+
95+
let repo = config.repositories.remove(idx);
96+
97+
// Clean up the repository's data directory
98+
if let Ok(repo_path) = repo.get_path() {
99+
if repo_path.exists() {
100+
fs::remove_dir_all(&repo_path).with_context(|| {
101+
format!("removing repository data at {}", repo_path.display())
102+
})?;
103+
}
104+
}
105+
106+
Ok(())
107+
})
108+
}
109+
}

0 commit comments

Comments
 (0)