@@ -2552,3 +2552,188 @@ url = \"git://example.org/repo3_origin\"
25522552 let remotes3 = String :: from_utf8 ( output3. stdout ) . expect ( "utf8 conversion failed" ) ;
25532553 assert ! ( remotes3. is_empty( ) ) ;
25542554}
2555+
2556+ #[ test]
2557+ fn clone_multiple_remotes_prefers_origin ( ) {
2558+ // Test that when cloning a repo with remotes aaa, origin, zzz:
2559+ // - It clones using the origin remote
2560+ // - It adds aaa and zzz as additional remotes
2561+ let temp = temp_folder ( ) ;
2562+
2563+ // Create source repos for each remote
2564+ create_local_repo ( & temp, "source_aaa" ) ;
2565+ create_local_repo ( & temp, "source_origin" ) ;
2566+ create_local_repo ( & temp, "source_zzz" ) ;
2567+
2568+ // Create state with multiple remotes including origin
2569+ let initial_state_toml = r#"[[repos]]
2570+ path = "cloned_repo"
2571+ tags = []
2572+
2573+ [repos.remotes.aaa]
2574+ name = "aaa"
2575+ url = "source_aaa"
2576+
2577+ [repos.remotes.origin]
2578+ name = "origin"
2579+ url = "source_origin"
2580+
2581+ [repos.remotes.zzz]
2582+ name = "zzz"
2583+ url = "source_zzz"
2584+ "# ;
2585+ write_gitopolis_state_toml ( & temp, initial_state_toml) ;
2586+
2587+ // Run clone
2588+ gitopolis_executable ( )
2589+ . current_dir ( & temp)
2590+ . args ( vec ! [ "clone" ] )
2591+ . assert ( )
2592+ . success ( )
2593+ . stdout ( predicate:: str:: contains ( "Cloning source_origin" ) ) ;
2594+
2595+ // Verify all three remotes exist in cloned repo with correct names
2596+ let cloned_path = temp. path ( ) . join ( "cloned_repo" ) ;
2597+
2598+ let remotes_output = Command :: new ( "git" )
2599+ . current_dir ( & cloned_path)
2600+ . args ( vec ! [ "remote" , "--verbose" ] )
2601+ . output ( )
2602+ . expect ( "git remote --verbose failed" ) ;
2603+ let remotes = String :: from_utf8 ( remotes_output. stdout ) . expect ( "utf8 conversion failed" ) ;
2604+
2605+ // git clone converts relative local paths to absolute, so strip the temp path prefix
2606+ let temp_path_str = temp. path ( ) . to_str ( ) . unwrap ( ) ;
2607+ let remotes = remotes. replace ( & format ! ( "{}/" , temp_path_str) , "" ) ;
2608+
2609+ let expected_remotes = r#"aaa source_aaa (fetch)
2610+ aaa source_aaa (push)
2611+ origin source_origin (fetch)
2612+ origin source_origin (push)
2613+ zzz source_zzz (fetch)
2614+ zzz source_zzz (push)
2615+ "# ;
2616+ assert_eq ! ( expected_remotes, remotes) ;
2617+ }
2618+
2619+ #[ test]
2620+ fn clone_multiple_remotes_uses_first_when_no_origin ( ) {
2621+ // Test that when cloning a repo with remotes aaa, bbb (no origin):
2622+ // - It clones using the first remote (aaa) alphabetically
2623+ // - The remote is named "aaa" (not "origin")
2624+ // - It adds bbb as an additional remote
2625+ let temp = temp_folder ( ) ;
2626+
2627+ // Create source repos for each remote
2628+ create_local_repo ( & temp, "source_aaa" ) ;
2629+ create_local_repo ( & temp, "source_bbb" ) ;
2630+
2631+ // Create state with multiple remotes, none named origin
2632+ let initial_state_toml = r#"[[repos]]
2633+ path = "cloned_repo"
2634+ tags = []
2635+
2636+ [repos.remotes.aaa]
2637+ name = "aaa"
2638+ url = "source_aaa"
2639+
2640+ [repos.remotes.bbb]
2641+ name = "bbb"
2642+ url = "source_bbb"
2643+ "# ;
2644+ write_gitopolis_state_toml ( & temp, initial_state_toml) ;
2645+
2646+ // Run clone
2647+ gitopolis_executable ( )
2648+ . current_dir ( & temp)
2649+ . args ( vec ! [ "clone" ] )
2650+ . assert ( )
2651+ . success ( )
2652+ . stdout ( predicate:: str:: contains ( "Cloning source_aaa" ) ) ;
2653+
2654+ // Verify remotes in cloned repo
2655+ let cloned_path = temp. path ( ) . join ( "cloned_repo" ) ;
2656+
2657+ let remotes_output = Command :: new ( "git" )
2658+ . current_dir ( & cloned_path)
2659+ . args ( vec ! [ "remote" , "--verbose" ] )
2660+ . output ( )
2661+ . expect ( "git remote --verbose failed" ) ;
2662+ let remotes = String :: from_utf8 ( remotes_output. stdout ) . expect ( "utf8 conversion failed" ) ;
2663+
2664+ // git clone converts relative local paths to absolute, so strip the temp path prefix
2665+ let temp_path_str = temp. path ( ) . to_str ( ) . unwrap ( ) ;
2666+ let remotes = remotes. replace ( & format ! ( "{}/" , temp_path_str) , "" ) ;
2667+
2668+ // Should have aaa and bbb remotes, NOT origin
2669+ let expected_remotes = r#"aaa source_aaa (fetch)
2670+ aaa source_aaa (push)
2671+ bbb source_bbb (fetch)
2672+ bbb source_bbb (push)
2673+ "# ;
2674+ assert_eq ! ( expected_remotes, remotes) ;
2675+ }
2676+
2677+ #[ test]
2678+ fn clone_skips_adding_remotes_when_repo_exists ( ) {
2679+ // Test that when a repo already exists, clone skips it entirely
2680+ // and doesn't try to add remotes (which would fail with "remote already exists")
2681+ let temp = temp_folder ( ) ;
2682+
2683+ // Create source repos
2684+ create_local_repo ( & temp, "source_origin" ) ;
2685+ create_local_repo ( & temp, "source_upstream" ) ;
2686+
2687+ // Pre-create the target repo with origin remote already configured
2688+ let cloned_path = temp. path ( ) . join ( "cloned_repo" ) ;
2689+ fs:: create_dir_all ( & cloned_path) . expect ( "create repo dir failed" ) ;
2690+ Command :: new ( "git" )
2691+ . current_dir ( & cloned_path)
2692+ . args ( vec ! [ "init" , "--initial-branch" , "main" ] )
2693+ . output ( )
2694+ . expect ( "git init failed" ) ;
2695+ Command :: new ( "git" )
2696+ . current_dir ( & cloned_path)
2697+ . args ( vec ! [ "remote" , "add" , "origin" , "existing_origin_url" ] )
2698+ . output ( )
2699+ . expect ( "git remote add failed" ) ;
2700+
2701+ // Create state with multiple remotes
2702+ let initial_state_toml = r#"[[repos]]
2703+ path = "cloned_repo"
2704+ tags = []
2705+
2706+ [repos.remotes.origin]
2707+ name = "origin"
2708+ url = "source_origin"
2709+
2710+ [repos.remotes.upstream]
2711+ name = "upstream"
2712+ url = "source_upstream"
2713+ "# ;
2714+ write_gitopolis_state_toml ( & temp, initial_state_toml) ;
2715+
2716+ // Run clone - should skip the existing repo and NOT try to add remotes
2717+ gitopolis_executable ( )
2718+ . current_dir ( & temp)
2719+ . args ( vec ! [ "clone" ] )
2720+ . assert ( )
2721+ . success ( )
2722+ . stdout ( predicate:: str:: contains ( "Already exists, skipped" ) )
2723+ // Should NOT have any warnings about failed remote adds
2724+ . stderr ( predicate:: str:: contains ( "Warning" ) . not ( ) ) ;
2725+
2726+ // Verify the original remote is unchanged (not overwritten)
2727+ let remotes_output = Command :: new ( "git" )
2728+ . current_dir ( & cloned_path)
2729+ . args ( vec ! [ "remote" , "--verbose" ] )
2730+ . output ( )
2731+ . expect ( "git remote --verbose failed" ) ;
2732+ let remotes = String :: from_utf8 ( remotes_output. stdout ) . expect ( "utf8 conversion failed" ) ;
2733+
2734+ // Should only have the original origin remote, not the upstream from config
2735+ let expected_remotes = r#"origin existing_origin_url (fetch)
2736+ origin existing_origin_url (push)
2737+ "# ;
2738+ assert_eq ! ( expected_remotes, remotes) ;
2739+ }
0 commit comments