diff --git a/gix/src/clone/mod.rs b/gix/src/clone/mod.rs index c3d50ad3d9..7ca001f8d9 100644 --- a/gix/src/clone/mod.rs +++ b/gix/src/clone/mod.rs @@ -102,7 +102,10 @@ impl PrepareFetch { mut create_opts: crate::create::Options, open_opts: crate::open::Options, ) -> Result { - create_opts.destination_must_be_empty = true; + if create_opts.destination_must_be_empty.is_none() { + create_opts.destination_must_be_empty = Some(true); + } + let mut repo = crate::ThreadSafeRepository::init_opts(path, kind, create_opts, open_opts)?.to_thread_local(); url.canonicalize(repo.options.current_dir_or_empty()) .map_err(|err| Error::CanonicalizeUrl { diff --git a/gix/src/create.rs b/gix/src/create.rs index d6395edaa4..b04349d375 100644 --- a/gix/src/create.rs +++ b/gix/src/create.rs @@ -113,7 +113,7 @@ pub struct Options { /// If true, and the kind of repository to create has a worktree, then the destination directory must be empty. /// /// By default repos with worktree can be initialized into a non-empty repository as long as there is no `.git` directory. - pub destination_must_be_empty: bool, + pub destination_must_be_empty: Option, /// If set, use these filesystem capabilities to populate the respective git-config fields. /// If `None`, the directory will be probed. pub fs_capabilities: Option, @@ -135,7 +135,7 @@ pub fn into( let mut dot_git = directory.into(); let bare = matches!(kind, Kind::Bare); - if bare || destination_must_be_empty { + if bare || destination_must_be_empty.unwrap_or(false) { let num_entries_in_dot_git = fs::read_dir(&dot_git) .or_else(|err| { if err.kind() == std::io::ErrorKind::NotFound { diff --git a/gix/tests/gix/clone.rs b/gix/tests/gix/clone.rs index c24ae33f3d..25d03f6b38 100644 --- a/gix/tests/gix/clone.rs +++ b/gix/tests/gix/clone.rs @@ -550,6 +550,36 @@ mod blocking_io { assure_index_entries_on_disk(&index, repo.workdir().expect("non-bare")); Ok(()) } + + #[test] + fn fetch_and_checkout_into_non_empty_directory() -> crate::Result { + let tmp = gix_testtools::tempfile::TempDir::new()?; + let existing_path = tmp.path().join("existing.txt"); + let existing_content = b"I was here before you"; + std::fs::write(&existing_path, existing_content)?; + + let mut prepare = gix::clone::PrepareFetch::new( + remote::repo("base").path(), + tmp.path(), + gix::create::Kind::WithWorktree, + gix::create::Options { + destination_must_be_empty: Some(false), + ..Default::default() + }, + restricted(), + )?; + let (mut checkout, _out) = + prepare.fetch_then_checkout(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?; + let (repo, _) = checkout.main_worktree(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?; + + let index = repo.index()?; + assert_eq!(index.entries().len(), 1, "All entries are known as per HEAD tree"); + assure_index_entries_on_disk(&index, repo.workdir().expect("non-bare")); + + assert_eq!(std::fs::read(&existing_path)?, existing_content); + Ok(()) + } + #[test] fn fetch_and_checkout_specific_ref() -> crate::Result { let tmp = gix_testtools::tempfile::TempDir::new()?; @@ -800,6 +830,25 @@ fn clone_and_destination_must_be_empty() -> crate::Result { Ok(()) } +#[test] +fn clone_with_worktree_and_destination_must_be_empty() -> crate::Result { + let tmp = gix_testtools::tempfile::TempDir::new()?; + std::fs::write(tmp.path().join("file"), b"hello")?; + match gix::clone::PrepareFetch::new( + remote::repo("base").path(), + tmp.path(), + gix::create::Kind::WithWorktree, + Default::default(), + restricted(), + ) { + Ok(_) => unreachable!("this should fail as the directory isn't empty"), + Err(err) => assert!(err + .to_string() + .starts_with("Refusing to initialize the non-empty directory as ")), + } + Ok(()) +} + #[test] fn clone_bare_into_empty_directory_and_early_drop() -> crate::Result { let tmp = gix_testtools::tempfile::TempDir::new()?; diff --git a/gix/tests/gix/init.rs b/gix/tests/gix/init.rs index 09e54c0a9d..be8fe7ff43 100644 --- a/gix/tests/gix/init.rs +++ b/gix/tests/gix/init.rs @@ -170,7 +170,7 @@ mod non_bare { tmp.path(), gix::create::Kind::WithWorktree, gix::create::Options { - destination_must_be_empty: true, + destination_must_be_empty: Some(true), ..Default::default() }, gix::open::Options::isolated(),