From 592b69d39a871178f9bf58a969ee12a573702001 Mon Sep 17 00:00:00 2001 From: j-walther <184943049+j-walther@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:28:14 +0200 Subject: [PATCH 1/7] Allow checkouts of empty repositories --- gix/src/clone/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gix/src/clone/mod.rs b/gix/src/clone/mod.rs index c3d50ad3d9..4ad1f49c68 100644 --- a/gix/src/clone/mod.rs +++ b/gix/src/clone/mod.rs @@ -99,10 +99,9 @@ impl PrepareFetch { mut url: gix_url::Url, path: &std::path::Path, kind: crate::create::Kind, - mut create_opts: crate::create::Options, + create_opts: crate::create::Options, open_opts: crate::open::Options, ) -> Result { - create_opts.destination_must_be_empty = 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 { From e6682a475bfaac1fa3914ca935f41c2940f0880f Mon Sep 17 00:00:00 2001 From: j-walther <184943049+j-walther@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:17:11 +0200 Subject: [PATCH 2/7] Add tests for checking out in non-empty folder --- gix/tests/gix/clone.rs | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/gix/tests/gix/clone.rs b/gix/tests/gix/clone.rs index c24ae33f3d..1c77da5f29 100644 --- a/gix/tests/gix/clone.rs +++ b/gix/tests/gix/clone.rs @@ -550,6 +550,33 @@ mod blocking_io { assure_index_entries_on_disk(&index, repo.workdir().expect("non-bare")); Ok(()) } + + #[test] + fn fetch_and_checkout_into_non_empty_directory_is_allowed_by_default() -> 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, + 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 +827,28 @@ 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, + gix::create::Options { + destination_must_be_empty: true, + ..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()?; From e6727f1ad2ec574d568393b78429e80232eea2d1 Mon Sep 17 00:00:00 2001 From: j-walther <184943049+j-walther@users.noreply.github.com> Date: Mon, 13 Apr 2026 13:17:52 +0200 Subject: [PATCH 3/7] Make destination_must_be_empty default to true --- gix/src/create.rs | 11 ++++++++++- gix/src/lib.rs | 10 +++++++++- gix/tests/gix/clone.rs | 12 ++++++------ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/gix/src/create.rs b/gix/src/create.rs index d6395edaa4..f85a746080 100644 --- a/gix/src/create.rs +++ b/gix/src/create.rs @@ -108,7 +108,7 @@ fn create_dir(p: &Path) -> Result<(), Error> { } /// Options for use in [`into()`]; -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone)] pub struct Options { /// If true, and the kind of repository to create has a worktree, then the destination directory must be empty. /// @@ -119,6 +119,15 @@ pub struct Options { pub fs_capabilities: Option, } +impl Default for Options { + fn default() -> Self { + Options { + destination_must_be_empty: true, + fs_capabilities: None, + } + } +} + /// Create a new `.git` repository of `kind` within the possibly non-existing `directory` /// and return its path. /// Note that this is a simple template-based initialization routine which should be accompanied with additional corrections diff --git a/gix/src/lib.rs b/gix/src/lib.rs index 9de19baf8d..8be8a786ff 100644 --- a/gix/src/lib.rs +++ b/gix/src/lib.rs @@ -293,7 +293,15 @@ pub fn open_with_environment_overrides(directory: impl Into) /// ``` #[allow(clippy::result_large_err)] pub fn init(directory: impl AsRef) -> Result { - ThreadSafeRepository::init(directory, create::Kind::WithWorktree, create::Options::default()).map(Into::into) + ThreadSafeRepository::init( + directory, + create::Kind::WithWorktree, + create::Options { + destination_must_be_empty: false, + ..Default::default() + }, + ) + .map(Into::into) } /// See [`ThreadSafeRepository::init()`], but returns a [`Repository`] instead. diff --git a/gix/tests/gix/clone.rs b/gix/tests/gix/clone.rs index 1c77da5f29..77331a6de9 100644 --- a/gix/tests/gix/clone.rs +++ b/gix/tests/gix/clone.rs @@ -552,7 +552,7 @@ mod blocking_io { } #[test] - fn fetch_and_checkout_into_non_empty_directory_is_allowed_by_default() -> crate::Result { + 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"; @@ -562,7 +562,10 @@ mod blocking_io { remote::repo("base").path(), tmp.path(), gix::create::Kind::WithWorktree, - Default::default(), + gix::create::Options { + destination_must_be_empty: false, + ..Default::default() + }, restricted(), )?; let (mut checkout, _out) = @@ -835,10 +838,7 @@ fn clone_with_worktree_and_destination_must_be_empty() -> crate::Result { remote::repo("base").path(), tmp.path(), gix::create::Kind::WithWorktree, - gix::create::Options { - destination_must_be_empty: true, - ..Default::default() - }, + Default::default(), restricted(), ) { Ok(_) => unreachable!("this should fail as the directory isn't empty"), From a1c482f4c7615528420707d649bc68c162e6fa67 Mon Sep 17 00:00:00 2001 From: j-walther <184943049+j-walther@users.noreply.github.com> Date: Tue, 14 Apr 2026 12:24:14 +0200 Subject: [PATCH 4/7] Trigger Build From 7ab8a06d60831619077e92e86add004091a23fe2 Mon Sep 17 00:00:00 2001 From: j-walther <184943049+j-walther@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:48:04 +0200 Subject: [PATCH 5/7] Fix ein binary --- gitoxide-core/src/repository/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gitoxide-core/src/repository/mod.rs b/gitoxide-core/src/repository/mod.rs index 53161d5696..c271c880f5 100644 --- a/gitoxide-core/src/repository/mod.rs +++ b/gitoxide-core/src/repository/mod.rs @@ -54,7 +54,10 @@ pub fn init(directory: Option) -> Result Date: Mon, 20 Apr 2026 16:43:12 +0200 Subject: [PATCH 6/7] Make destination_must_be_empty Option --- gitoxide-core/src/repository/mod.rs | 5 +---- gix/src/clone/mod.rs | 6 +++++- gix/src/create.rs | 15 +++------------ gix/src/lib.rs | 10 +--------- gix/tests/gix/clone.rs | 2 +- gix/tests/gix/init.rs | 2 +- 6 files changed, 12 insertions(+), 28 deletions(-) diff --git a/gitoxide-core/src/repository/mod.rs b/gitoxide-core/src/repository/mod.rs index c271c880f5..53161d5696 100644 --- a/gitoxide-core/src/repository/mod.rs +++ b/gitoxide-core/src/repository/mod.rs @@ -54,10 +54,7 @@ pub fn init(directory: Option) -> Result Result { + 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 f85a746080..8820851c67 100644 --- a/gix/src/create.rs +++ b/gix/src/create.rs @@ -108,26 +108,17 @@ fn create_dir(p: &Path) -> Result<(), Error> { } /// Options for use in [`into()`]; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Default)] 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, } -impl Default for Options { - fn default() -> Self { - Options { - destination_must_be_empty: true, - fs_capabilities: None, - } - } -} - /// Create a new `.git` repository of `kind` within the possibly non-existing `directory` /// and return its path. /// Note that this is a simple template-based initialization routine which should be accompanied with additional corrections @@ -144,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_default() { 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/src/lib.rs b/gix/src/lib.rs index 8be8a786ff..9de19baf8d 100644 --- a/gix/src/lib.rs +++ b/gix/src/lib.rs @@ -293,15 +293,7 @@ pub fn open_with_environment_overrides(directory: impl Into) /// ``` #[allow(clippy::result_large_err)] pub fn init(directory: impl AsRef) -> Result { - ThreadSafeRepository::init( - directory, - create::Kind::WithWorktree, - create::Options { - destination_must_be_empty: false, - ..Default::default() - }, - ) - .map(Into::into) + ThreadSafeRepository::init(directory, create::Kind::WithWorktree, create::Options::default()).map(Into::into) } /// See [`ThreadSafeRepository::init()`], but returns a [`Repository`] instead. diff --git a/gix/tests/gix/clone.rs b/gix/tests/gix/clone.rs index 77331a6de9..25d03f6b38 100644 --- a/gix/tests/gix/clone.rs +++ b/gix/tests/gix/clone.rs @@ -563,7 +563,7 @@ mod blocking_io { tmp.path(), gix::create::Kind::WithWorktree, gix::create::Options { - destination_must_be_empty: false, + destination_must_be_empty: Some(false), ..Default::default() }, restricted(), 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(), From a71f6ad9e16b4514ac50bc4d44229701e4b65a54 Mon Sep 17 00:00:00 2001 From: Jan Walther Date: Tue, 21 Apr 2026 11:49:51 +0200 Subject: [PATCH 7/7] Update gix/src/create.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- gix/src/create.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gix/src/create.rs b/gix/src/create.rs index 8820851c67..b04349d375 100644 --- a/gix/src/create.rs +++ b/gix/src/create.rs @@ -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.unwrap_or_default() { + 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 {