@@ -20,6 +20,9 @@ mod blocking_io {
2020 use gix_ref:: TargetRef ;
2121 use gix_refspec:: parse:: Operation ;
2222
23+ const EXISTING_CONTENT : & [ u8 ] = b"Pre-existing user content.\n " ;
24+ const EXISTING_HEAD_CONTENT : & [ u8 ] = b"ref: refs/heads/pre-existing\n " ;
25+
2326 fn shallow_ids ( repo : & gix:: Repository , expected : & ' static str ) -> crate :: Result < Vec < gix:: ObjectId > > {
2427 let commits = repo. shallow_commits ( ) ?. expect ( expected) ;
2528 Ok ( std:: iter:: once ( commits. head )
@@ -587,14 +590,13 @@ mod blocking_io {
587590
588591 #[ test]
589592 fn fetch_and_checkout_into_non_empty_directory ( ) -> crate :: Result {
590- let tmp = gix_testtools:: tempfile:: TempDir :: new ( ) ?;
591- let existing_path = tmp. path ( ) . join ( "existing.txt" ) ;
592- let existing_content = b"I was here before you" ;
593- std:: fs:: write ( & existing_path, existing_content) ?;
593+ let fixture = gix_testtools:: scripted_fixture_writable ( "make_clone_destinations.sh" ) ?;
594+ let destination = fixture. path ( ) . join ( "non-empty" ) ;
595+ let existing_path = destination. join ( "existing.txt" ) ;
594596
595597 let mut prepare = gix:: clone:: PrepareFetch :: new (
596598 remote:: repo ( "base" ) . path ( ) ,
597- tmp . path ( ) ,
599+ & destination ,
598600 gix:: create:: Kind :: WithWorktree ,
599601 gix:: create:: Options {
600602 destination_must_be_empty : Some ( false ) ,
@@ -610,20 +612,94 @@ mod blocking_io {
610612 assert_eq ! ( index. entries( ) . len( ) , 1 , "All entries are known as per HEAD tree" ) ;
611613 assure_index_entries_on_disk ( & index, repo. workdir ( ) . expect ( "non-bare" ) ) ;
612614
613- assert_eq ! ( std:: fs:: read( & existing_path) ?, existing_content ) ;
615+ assert_eq ! ( std:: fs:: read( & existing_path) ?, EXISTING_CONTENT ) ;
614616 Ok ( ( ) )
615617 }
616618
617619 #[ test]
618- fn drop_after_failed_fetch_into_non_empty_directory_preserves_pre_existing_files ( ) -> crate :: Result {
619- let tmp = gix_testtools:: tempfile:: TempDir :: new ( ) ?;
620- let existing_path = tmp. path ( ) . join ( "existing.txt" ) ;
621- let existing_content = b"I was here before you" ;
622- std:: fs:: write ( & existing_path, existing_content) ?;
620+ fn fetch_and_checkout_into_non_empty_directory_does_not_overwrite_pre_existing_tracked_file ( ) -> crate :: Result {
621+ let fixture = gix_testtools:: scripted_fixture_writable ( "make_clone_destinations.sh" ) ?;
622+ let destination = fixture. path ( ) . join ( "non-empty-with-conflicting-file" ) ;
623+ let existing_path = destination. join ( "file" ) ;
624+ let remote_file_content = std:: fs:: read ( remote:: repo ( "base" ) . workdir ( ) . expect ( "non-bare" ) . join ( "file" ) ) ?;
625+ assert_ne ! (
626+ EXISTING_CONTENT , remote_file_content,
627+ "the fixture must differ from the file that checkout would write"
628+ ) ;
623629
624630 let mut prepare = gix:: clone:: PrepareFetch :: new (
625631 remote:: repo ( "base" ) . path ( ) ,
626- tmp. path ( ) ,
632+ & destination,
633+ gix:: create:: Kind :: WithWorktree ,
634+ gix:: create:: Options {
635+ destination_must_be_empty : Some ( false ) ,
636+ ..Default :: default ( )
637+ } ,
638+ restricted ( ) ,
639+ ) ?;
640+ let ( mut checkout, _out) = prepare. fetch_then_checkout ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
641+ let ( repo, outcome) = checkout. main_worktree ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
642+
643+ assert_eq ! (
644+ std:: fs:: read( & existing_path) ?,
645+ EXISTING_CONTENT ,
646+ "checkout must not overwrite the pre-existing tracked path"
647+ ) ;
648+ assert_eq ! ( repo. index( ) ?. entries( ) . len( ) , 1 , "the index is still written" ) ;
649+ assert_eq ! (
650+ outcome. collisions,
651+ [ gix_worktree_state:: checkout:: Collision {
652+ path: BString :: from( "file" ) ,
653+ error_kind: std:: io:: ErrorKind :: AlreadyExists
654+ } ] ,
655+ "the pre-existing tracked path is reported as a normal checkout collision"
656+ ) ;
657+ Ok ( ( ) )
658+ }
659+
660+ #[ test]
661+ fn fetch_and_checkout_into_non_empty_directory_with_existing_dot_git_is_rejected ( ) -> crate :: Result {
662+ let fixture = gix_testtools:: scripted_fixture_writable ( "make_clone_destinations.sh" ) ?;
663+ let destination = fixture. path ( ) . join ( "non-empty-with-dot-git" ) ;
664+ let existing_path = destination. join ( "existing.txt" ) ;
665+ let dot_git = destination. join ( ".git" ) ;
666+ let head_path = dot_git. join ( "HEAD" ) ;
667+
668+ let err = gix:: clone:: PrepareFetch :: new (
669+ remote:: repo ( "base" ) . path ( ) ,
670+ & destination,
671+ gix:: create:: Kind :: WithWorktree ,
672+ gix:: create:: Options {
673+ destination_must_be_empty : Some ( false ) ,
674+ ..Default :: default ( )
675+ } ,
676+ restricted ( ) ,
677+ )
678+ . map ( drop)
679+ . expect_err ( "an existing .git directory must not be reused for clone" ) ;
680+
681+ assert ! (
682+ matches!(
683+ err,
684+ gix:: clone:: Error :: Init ( gix:: init:: Error :: Init ( gix:: create:: Error :: DirectoryExists { ref path } ) )
685+ if * path == dot_git
686+ ) ,
687+ "unexpected error: {err}"
688+ ) ;
689+ assert_eq ! ( std:: fs:: read( & existing_path) ?, EXISTING_CONTENT ) ;
690+ assert_eq ! ( std:: fs:: read( & head_path) ?, EXISTING_HEAD_CONTENT ) ;
691+ Ok ( ( ) )
692+ }
693+
694+ #[ test]
695+ fn drop_after_failed_fetch_into_non_empty_directory_preserves_destination ( ) -> crate :: Result {
696+ let fixture = gix_testtools:: scripted_fixture_writable ( "make_clone_destinations.sh" ) ?;
697+ let destination = fixture. path ( ) . join ( "non-empty" ) ;
698+ let existing_path = destination. join ( "existing.txt" ) ;
699+
700+ let mut prepare = gix:: clone:: PrepareFetch :: new (
701+ remote:: repo ( "base" ) . path ( ) ,
702+ & destination,
627703 gix:: create:: Kind :: WithWorktree ,
628704 gix:: create:: Options {
629705 destination_must_be_empty : Some ( false ) ,
@@ -640,12 +716,12 @@ mod blocking_io {
640716
641717 assert_eq ! (
642718 std:: fs:: read( & existing_path) ?,
643- existing_content ,
719+ EXISTING_CONTENT ,
644720 "pre-existing user files must survive a failed clone+drop"
645721 ) ;
646722 assert ! (
647- !tmp . path ( ) . join( ".git" ) . exists ( ) ,
648- "only the .git directory we created should have been cleaned up "
723+ destination . join( ".git" ) . is_dir ( ) ,
724+ "the .git directory we created should remain for user cleanup "
649725 ) ;
650726 Ok ( ( ) )
651727 }
@@ -899,21 +975,21 @@ fn clone_and_destination_must_be_empty() -> crate::Result {
899975
900976#[ test]
901977fn clone_with_worktree_and_destination_must_be_empty ( ) -> crate :: Result {
902- let tmp = gix_testtools:: tempfile :: TempDir :: new ( ) ?;
903- std :: fs :: write ( tmp . path ( ) . join ( "file" ) , b"hello" ) ? ;
904- match gix:: clone:: PrepareFetch :: new (
978+ let fixture = gix_testtools:: scripted_fixture_writable ( "make_clone_destinations.sh" ) ?;
979+ let destination = fixture . path ( ) . join ( "non-empty" ) ;
980+ let err = gix:: clone:: PrepareFetch :: new (
905981 remote:: repo ( "base" ) . path ( ) ,
906- tmp . path ( ) ,
982+ & destination ,
907983 gix:: create:: Kind :: WithWorktree ,
908984 Default :: default ( ) ,
909985 restricted ( ) ,
910- ) {
911- Ok ( _ ) => unreachable ! ( "this should fail as the directory isn't empty" ) ,
912- Err ( err ) => assert ! (
913- err . to_string ( )
914- . starts_with ( "Refusing to initialize the non-empty directory as " )
915- ) ,
916- }
986+ )
987+ . map ( drop )
988+ . expect_err ( "this should fail as the directory isn't empty" ) ;
989+ assert ! (
990+ err . to_string ( )
991+ . starts_with ( "Refusing to initialize the non-empty directory as " )
992+ ) ;
917993 Ok ( ( ) )
918994}
919995
0 commit comments