@@ -12,21 +12,29 @@ use walkdir::WalkDir;
1212pub struct InitCommand {
1313 pub output : String ,
1414 pub overwrite : bool ,
15+ pub supplement : bool ,
1516}
1617
1718#[ async_trait]
1819impl 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}
0 commit comments