1use serde::{Deserialize, Serialize};
2use std::path::Path;
3
4#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7pub struct LitConfig {
8 #[serde(default)]
9 pub core: CoreConfig,
10 #[serde(default)]
11 pub agent: AgentConfig,
12 #[serde(default)]
13 pub merge: MergeConfig,
14 #[serde(default)]
15 pub security: SecurityConfig,
16 #[serde(default)]
17 pub performance: PerformanceConfig,
18 #[serde(default)]
19 pub lfs: LfsConfig,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct CoreConfig {
24 pub default_branch: String,
25 pub default_output: String,
26}
27
28impl Default for CoreConfig {
29 fn default() -> Self {
30 CoreConfig {
31 default_branch: "main".to_string(),
32 default_output: "json".to_string(),
33 }
34 }
35}
36
37#[derive(Debug, Clone, Default, Serialize, Deserialize)]
38pub struct AgentConfig {
39 pub auto_sign: bool,
40 pub default_metadata: Option<serde_json::Value>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct MergeConfig {
45 pub default_strategy: String,
46 pub auto_resolve: bool,
47}
48
49impl Default for MergeConfig {
50 fn default() -> Self {
51 MergeConfig {
52 default_strategy: "recursive".to_string(),
53 auto_resolve: false,
54 }
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct SecurityConfig {
60 pub encryption: String,
61 pub fips_mode: bool,
62 pub audit_log: bool,
63}
64
65impl Default for SecurityConfig {
66 fn default() -> Self {
67 SecurityConfig {
68 encryption: "aes-256-gcm".to_string(),
69 fips_mode: false,
70 audit_log: true,
71 }
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct PerformanceConfig {
77 pub parallel_io: bool,
79 pub threads: usize,
81 pub auto_pack_threshold: usize,
83 pub lfs_threshold: u64,
85}
86
87impl Default for PerformanceConfig {
88 fn default() -> Self {
89 PerformanceConfig {
90 parallel_io: true,
91 threads: 0,
92 auto_pack_threshold: 1000,
93 lfs_threshold: 10 * 1024 * 1024,
94 }
95 }
96}
97
98#[derive(Debug, Clone, Default, Serialize, Deserialize)]
99pub struct LfsConfig {
100 pub enabled: bool,
102 pub track_patterns: Vec<String>,
104}
105
106impl LitConfig {
107 pub fn load(repo_path: Option<&Path>) -> Self {
110 let mut config = LitConfig::default();
111
112 if let Some(home) = dirs::home_dir() {
114 let global_path = home.join(".litconfig.toml");
115 if let Ok(content) = std::fs::read_to_string(&global_path) {
116 if let Ok(global) = toml::from_str::<LitConfig>(&content) {
117 config = config.merge_with(global);
118 }
119 }
120 }
121
122 if let Some(repo) = repo_path {
124 let local_path = repo.join(".lit").join("config.toml");
125 if let Ok(content) = std::fs::read_to_string(&local_path) {
126 if let Ok(local) = toml::from_str::<LitConfig>(&content) {
127 config = config.merge_with(local);
128 }
129 }
130 }
131
132 if let Ok(v) = std::env::var("LIT_DEFAULT_BRANCH") {
134 config.core.default_branch = v;
135 }
136 if let Ok(v) = std::env::var("LIT_OUTPUT") {
137 config.core.default_output = v;
138 }
139 if let Ok(v) = std::env::var("LIT_FIPS_MODE") {
140 config.security.fips_mode = v == "true" || v == "1";
141 }
142 if let Ok(v) = std::env::var("LIT_PARALLEL") {
143 config.performance.parallel_io = v != "false" && v != "0";
144 }
145
146 config
147 }
148
149 pub fn save_local(&self, repo_path: &Path) -> Result<(), String> {
151 let config_path = repo_path.join(".lit").join("config.toml");
152 let content = toml::to_string_pretty(self)
153 .map_err(|e| format!("Failed to serialize config: {}", e))?;
154 std::fs::write(&config_path, content).map_err(|e| format!("Failed to write config: {}", e))
155 }
156
157 pub fn save_global(&self) -> Result<(), String> {
159 let home = dirs::home_dir().ok_or("Could not determine home directory")?;
160 let config_path = home.join(".litconfig.toml");
161 let content = toml::to_string_pretty(self)
162 .map_err(|e| format!("Failed to serialize config: {}", e))?;
163 std::fs::write(&config_path, content).map_err(|e| format!("Failed to write config: {}", e))
164 }
165
166 pub fn get(&self, key: &str) -> Option<String> {
168 match key {
169 "core.default_branch" => Some(self.core.default_branch.clone()),
170 "core.default_output" => Some(self.core.default_output.clone()),
171 "agent.auto_sign" => Some(self.agent.auto_sign.to_string()),
172 "merge.default_strategy" => Some(self.merge.default_strategy.clone()),
173 "merge.auto_resolve" => Some(self.merge.auto_resolve.to_string()),
174 "security.encryption" => Some(self.security.encryption.clone()),
175 "security.fips_mode" => Some(self.security.fips_mode.to_string()),
176 "security.audit_log" => Some(self.security.audit_log.to_string()),
177 "performance.parallel_io" => Some(self.performance.parallel_io.to_string()),
178 "performance.threads" => Some(self.performance.threads.to_string()),
179 "performance.auto_pack_threshold" => {
180 Some(self.performance.auto_pack_threshold.to_string())
181 }
182 "performance.lfs_threshold" => Some(self.performance.lfs_threshold.to_string()),
183 "lfs.enabled" => Some(self.lfs.enabled.to_string()),
184 _ => None,
185 }
186 }
187
188 pub fn set(&mut self, key: &str, value: &str) -> Result<(), String> {
190 match key {
191 "core.default_branch" => self.core.default_branch = value.to_string(),
192 "core.default_output" => self.core.default_output = value.to_string(),
193 "agent.auto_sign" => {
194 self.agent.auto_sign = value == "true" || value == "1";
195 }
196 "merge.default_strategy" => self.merge.default_strategy = value.to_string(),
197 "merge.auto_resolve" => {
198 self.merge.auto_resolve = value == "true" || value == "1";
199 }
200 "security.encryption" => self.security.encryption = value.to_string(),
201 "security.fips_mode" => {
202 self.security.fips_mode = value == "true" || value == "1";
203 }
204 "security.audit_log" => {
205 self.security.audit_log = value == "true" || value == "1";
206 }
207 "performance.parallel_io" => {
208 self.performance.parallel_io = value == "true" || value == "1";
209 }
210 "performance.threads" => {
211 self.performance.threads = value
212 .parse()
213 .map_err(|_| format!("Invalid thread count: {}", value))?;
214 }
215 "performance.auto_pack_threshold" => {
216 self.performance.auto_pack_threshold = value
217 .parse()
218 .map_err(|_| format!("Invalid threshold: {}", value))?;
219 }
220 "performance.lfs_threshold" => {
221 self.performance.lfs_threshold = value
222 .parse()
223 .map_err(|_| format!("Invalid threshold: {}", value))?;
224 }
225 "lfs.enabled" => {
226 self.lfs.enabled = value == "true" || value == "1";
227 }
228 _ => return Err(format!("Unknown config key: {}", key)),
229 }
230 Ok(())
231 }
232
233 pub fn entries(&self) -> Vec<(String, String)> {
235 vec![
236 (
237 "core.default_branch".into(),
238 self.core.default_branch.clone(),
239 ),
240 (
241 "core.default_output".into(),
242 self.core.default_output.clone(),
243 ),
244 ("agent.auto_sign".into(), self.agent.auto_sign.to_string()),
245 (
246 "merge.default_strategy".into(),
247 self.merge.default_strategy.clone(),
248 ),
249 (
250 "merge.auto_resolve".into(),
251 self.merge.auto_resolve.to_string(),
252 ),
253 (
254 "security.encryption".into(),
255 self.security.encryption.clone(),
256 ),
257 (
258 "security.fips_mode".into(),
259 self.security.fips_mode.to_string(),
260 ),
261 (
262 "security.audit_log".into(),
263 self.security.audit_log.to_string(),
264 ),
265 (
266 "performance.parallel_io".into(),
267 self.performance.parallel_io.to_string(),
268 ),
269 (
270 "performance.threads".into(),
271 self.performance.threads.to_string(),
272 ),
273 (
274 "performance.auto_pack_threshold".into(),
275 self.performance.auto_pack_threshold.to_string(),
276 ),
277 (
278 "performance.lfs_threshold".into(),
279 self.performance.lfs_threshold.to_string(),
280 ),
281 ("lfs.enabled".into(), self.lfs.enabled.to_string()),
282 ]
283 }
284
285 fn merge_with(mut self, other: LitConfig) -> Self {
287 if other.core.default_branch != CoreConfig::default().default_branch {
289 self.core.default_branch = other.core.default_branch;
290 }
291 if other.core.default_output != CoreConfig::default().default_output {
292 self.core.default_output = other.core.default_output;
293 }
294 if other.agent.auto_sign {
295 self.agent.auto_sign = true;
296 }
297 if other.agent.default_metadata.is_some() {
298 self.agent.default_metadata = other.agent.default_metadata;
299 }
300 if other.merge.default_strategy != MergeConfig::default().default_strategy {
301 self.merge.default_strategy = other.merge.default_strategy;
302 }
303 if other.merge.auto_resolve {
304 self.merge.auto_resolve = true;
305 }
306 if other.security.fips_mode {
307 self.security.fips_mode = true;
308 }
309 if !other.performance.parallel_io {
310 self.performance.parallel_io = false;
311 }
312 if other.performance.threads != 0 {
313 self.performance.threads = other.performance.threads;
314 }
315 if other.lfs.enabled {
316 self.lfs.enabled = true;
317 }
318 if !other.lfs.track_patterns.is_empty() {
319 self.lfs.track_patterns = other.lfs.track_patterns;
320 }
321 self
322 }
323}