Skip to content

Commit 6b18573

Browse files
author
nicos_backbase
committed
feat: add supplement option to init command
1 parent f28e32f commit 6b18573

2 files changed

Lines changed: 179 additions & 22 deletions

File tree

src/commands/init.rs

Lines changed: 163 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,29 @@ use walkdir::WalkDir;
1212
pub struct InitCommand {
1313
pub output: String,
1414
pub overwrite: bool,
15+
pub supplement: bool,
1516
}
1617

1718
#[async_trait]
1819
impl Command for InitCommand {
1920
async fn execute(&self, _context: &CommandContext) -> Result<()> {
20-
if Path::new(&self.output).exists() && !self.overwrite {
21-
return Err(anyhow::anyhow!(
22-
"Output file '{}' already exists. Use --overwrite to replace it.",
23-
self.output
24-
));
25-
}
21+
// Load existing config if supplementing, otherwise check for overwrite
22+
let mut existing_config = if self.supplement && Path::new(&self.output).exists() {
23+
println!("{}", "Loading existing configuration...".green());
24+
Config::load(&self.output)?
25+
} else {
26+
if Path::new(&self.output).exists() && !self.overwrite {
27+
return Err(anyhow::anyhow!(
28+
"Output file '{}' already exists. Use --overwrite to replace it or --supplement to add new repositories.",
29+
self.output
30+
));
31+
}
32+
Config::new()
33+
};
2634

2735
println!("{}", "Discovering Git repositories...".green());
2836

29-
let mut repositories = Vec::new();
37+
let mut discovered_repositories = Vec::new();
3038
let current_dir = std::env::current_dir()?;
3139

3240
for entry in WalkDir::new(&current_dir)
@@ -50,31 +58,80 @@ impl Command for InitCommand {
5058
.to_string(),
5159
)
5260
.build();
53-
repositories.push(repo);
61+
discovered_repositories.push(repo);
5462
}
5563
}
5664
}
5765

58-
if repositories.is_empty() {
66+
if discovered_repositories.is_empty() {
5967
println!(
6068
"{}",
6169
"No Git repositories found in current directory".yellow()
6270
);
63-
return Ok(());
71+
if !self.supplement {
72+
return Ok(());
73+
}
6474
}
6575

66-
println!(
67-
"{}",
68-
format!("Found {} repositories", repositories.len()).green()
69-
);
76+
let mut added_count = 0;
77+
let has_existing_config = Path::new(&self.output).exists();
7078

71-
let config = Config { repositories };
72-
config.save(&self.output)?;
79+
if self.supplement {
80+
// Add only new repositories (not already in config)
81+
for repo in discovered_repositories {
82+
if existing_config.get_repository(&repo.name).is_none() {
83+
existing_config.add_repository(repo)?;
84+
added_count += 1;
85+
} else {
86+
println!(
87+
"{}",
88+
format!(
89+
"Repository '{}' already exists in config, skipping",
90+
repo.name
91+
)
92+
.yellow()
93+
);
94+
}
95+
}
96+
97+
if added_count > 0 {
98+
println!(
99+
"{}",
100+
format!("Added {} new repositories to existing config", added_count).green()
101+
);
102+
} else {
103+
println!("{}", "No new repositories found to add".yellow());
104+
}
73105

74-
println!(
75-
"{}",
76-
format!("Configuration saved to '{}'", self.output).green()
77-
);
106+
// Only save if we have new repositories to add or if config already existed
107+
if added_count > 0 || has_existing_config {
108+
existing_config.save(&self.output)?;
109+
110+
if added_count > 0 {
111+
println!(
112+
"{}",
113+
format!(
114+
"Configuration updated with {} new repositories in '{}'",
115+
added_count, self.output
116+
)
117+
.green()
118+
);
119+
}
120+
}
121+
} else {
122+
// Replace mode - use all discovered repositories
123+
existing_config.repositories = discovered_repositories;
124+
println!(
125+
"{}",
126+
format!("Found {} repositories", existing_config.repositories.len()).green()
127+
);
128+
129+
existing_config.save(&self.output)?;
130+
println!(
131+
"{}",
132+
format!("Configuration saved to '{}'", self.output).green()
133+
);
134+
}
78135

79136
Ok(())
80137
}
@@ -114,6 +171,7 @@ mod tests {
114171
let command = InitCommand {
115172
output: output_path.to_string_lossy().to_string(),
116173
overwrite: false,
174+
supplement: false,
117175
};
118176

119177
let context = CommandContext {
@@ -146,6 +204,7 @@ mod tests {
146204
let command = InitCommand {
147205
output: output_path.to_string_lossy().to_string(),
148206
overwrite: false, // Should not overwrite
207+
supplement: false,
149208
};
150209

151210
let context = CommandContext {
@@ -172,9 +231,93 @@ mod tests {
172231
let command = InitCommand {
173232
output: "test.yaml".to_string(),
174233
overwrite: true,
234+
supplement: false,
175235
};
176236

177237
assert_eq!(command.output, "test.yaml");
178238
assert!(command.overwrite);
239+
assert!(!command.supplement);
240+
}
241+
242+
#[tokio::test]
243+
async fn test_init_command_supplement_with_existing_config() {
244+
let temp_dir = TempDir::new().unwrap();
245+
let output_path = temp_dir.path().join("existing-config.yaml");
246+
247+
// Create existing config with one repository
248+
let existing_config = Config {
249+
repositories: vec![crate::config::Repository::new(
250+
"existing-repo".to_string(),
251+
"git@github.com:owner/existing-repo.git".to_string(),
252+
)],
253+
};
254+
existing_config
255+
.save(&output_path.to_string_lossy())
256+
.unwrap();
257+
258+
let original_dir = std::env::current_dir().unwrap();
259+
260+
// Change to temp directory (empty, no git repos)
261+
std::env::set_current_dir(temp_dir.path()).unwrap();
262+
263+
let command = InitCommand {
264+
output: output_path.to_string_lossy().to_string(),
265+
overwrite: false,
266+
supplement: true, // Should supplement existing config
267+
};
268+
269+
let context = CommandContext {
270+
config: Config {
271+
repositories: vec![],
272+
},
273+
tag: None,
274+
repos: None,
275+
parallel: false,
276+
};
277+
278+
let result = command.execute(&context).await;
279+
assert!(result.is_ok()); // Should succeed
280+
281+
// Verify config still contains the existing repository
282+
let updated_config = Config::load(&output_path.to_string_lossy()).unwrap();
283+
assert_eq!(updated_config.repositories.len(), 1);
284+
assert_eq!(updated_config.repositories[0].name, "existing-repo");
285+
286+
// Restore original directory
287+
std::env::set_current_dir(original_dir).unwrap();
288+
}
289+
290+
#[tokio::test]
291+
async fn test_init_command_supplement_without_existing_config() {
292+
let temp_dir = TempDir::new().unwrap();
293+
let output_path = temp_dir.path().join("new-config.yaml");
294+
let original_dir = std::env::current_dir().unwrap();
295+
296+
// Change to temp directory (empty, no git repos)
297+
std::env::set_current_dir(temp_dir.path()).unwrap();
298+
299+
let command = InitCommand {
300+
output: output_path.to_string_lossy().to_string(),
301+
overwrite: false,
302+
supplement: true, // Should create new config since none exists
303+
};
304+
305+
let context = CommandContext {
306+
config: Config {
307+
repositories: vec![],
308+
},
309+
tag: None,
310+
repos: None,
311+
parallel: false,
312+
};
313+
314+
let result = command.execute(&context).await;
315+
assert!(result.is_ok()); // Should succeed
316+
317+
// Verify no config file was created (no repos found)
318+
assert!(!output_path.exists());
319+
320+
// Restore original directory
321+
std::env::set_current_dir(original_dir).unwrap();
179322
}
180323
}

src/main.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ enum Commands {
134134
/// Overwrite existing file if it exists
135135
#[arg(long)]
136136
overwrite: bool,
137+
138+
/// Supplement existing config with newly discovered repositories
139+
#[arg(long)]
140+
supplement: bool,
137141
},
138142
}
139143

@@ -233,15 +237,25 @@ async fn main() -> Result<()> {
233237
};
234238
RemoveCommand.execute(&context).await?;
235239
}
236-
Commands::Init { output, overwrite } => {
240+
Commands::Init {
241+
output,
242+
overwrite,
243+
supplement,
244+
} => {
237245
// Init command doesn't need config since it creates one
238246
let context = CommandContext {
239247
config: Config::new(),
240248
tag: None,
241249
parallel: false,
242250
repos: None,
243251
};
244-
InitCommand { output, overwrite }.execute(&context).await?;
252+
InitCommand {
253+
output,
254+
overwrite,
255+
supplement,
256+
}
257+
.execute(&context)
258+
.await?;
245259
}
246260
}
247261

0 commit comments

Comments
 (0)