use std::{ path::Path, sync::atomic::{AtomicBool, Ordering},}; use gix_features::progress::{NestedProgress, Progress};use gix_ref::transaction::{LogChange, PreviousValue, RefEdit, RefLog}; use crate::config::{ cache::util::ApplyLeniency, tree::{Core, Extensions, Worktree},}; /// The kind of `HEAD` to install in a newly added worktree.#[derive(Clone, Debug, Eq, PartialEq)]pub enum Head { /// Check out an existing local branch in the default reference namespace. Attached(gix_ref::FullName), /// Check out a commit with a detached `HEAD`. Detached(gix_hash::ObjectId),} /// The error returned by [`Repository::add_worktree()`][crate::Repository::add_worktree()].#[derive(Debug, thiserror::Error)]#[expect(missing_docs)]pub enum Error { #[error(transparent)] ConfigBoolean(#[from] crate::config::boolean::Error), #[error("Could not enable relative worktrees in the shared configuration")] Configure(#[from] crate::config::file_mut::Error), #[error("Could not set the repository format for relative worktrees")] SetConfig(#[from] gix_config::file::set_raw_value::Error), #[error("Could not upgrade the repository format for relative worktrees")] RepositoryFormat(#[from] crate::config::Error), #[error("Cannot upgrade repository format with unsupported extension {name:?}")] UnsupportedExtension { name: String }, #[error("Could not read the source worktree configuration")] ReadWorktreeConfig(#[source] gix_config::file::init::from_paths::Error), #[error("Could not write the new worktree configuration")] WriteWorktreeConfig(#[source] std::io::Error), #[error("{name:?} is not a local branch")] NotLocalBranch { name: gix_ref::FullName }, #[error("The local branch {name:?} is checked out in {worktree_dirs:?}")] CheckedOut { name: gix_ref::FullName, worktree_dirs: Vec<std::path::PathBuf>, }, #[error("The worktree destination {destination:?} is already registered")] DestinationRegistered { destination: std::path::PathBuf }, #[error("Failed to read or iterate worktree directories")] WorktreeListing(#[source] std::io::Error), #[error("Could not open a worktree repository")] OpenWorktreeRepo(#[source] crate::open::Error), #[error("Failed to follow a symbolic reference while inspecting worktrees")] FollowSymref(#[source] gix_ref::file::find::existing::Error), #[error("The local branch could not be found")] FindBranch(#[from] crate::reference::find::existing::Error), #[error("The local branch could not be peeled to a commit")] PeelBranch(#[from] crate::reference::peel::to_kind::Error), #[error("The detached target is not an existing commit")] FindDetachedCommit(#[from] crate::object::find::existing::with_conversion::Error), #[error("Could not decode the commit")] DecodeCommit(#[from] gix_object::decode::Error), #[error("Could not prepare the linked worktree")] Prepare(#[source] std::io::Error), #[error("Could not write the linked worktree HEAD")] WriteHead(#[source] std::io::Error), #[error("Could not initialize the linked worktree HEAD and its reflog")] InitializeHead(#[source] crate::reference::edit::Error), #[error("Could not create an index from the target tree")] IndexFromTree(#[from] crate::repository::index_from_tree::Error), #[error(transparent)] CheckoutOptions(#[from] crate::config::checkout_options::Error), #[error("Failed to reopen the object database for checkout")] OpenArcOdb(#[source] std::io::Error), #[error(transparent)] Checkout(#[from] gix_worktree_state::checkout::Error), #[error("Adding the worktree was interrupted")] Interrupted, #[error("Could not write the linked worktree index")] WriteIndex(#[from] gix_index::file::write::Error), #[error("Could not finish adding the linked worktree")] Persist(#[source] std::io::Error),} impl crate::Repository { /// Add and check out a linked worktree at `destination` with the given `head`. /// /// Attached heads must name an existing local branch which isn't checked out or reserved by an ongoing /// bisect or rebase in any worktree. /// Reference namespaces are ignored for branch lookup, occupancy checks, and the returned repository, like /// `git worktree add`. The source repository's namespace is preserved. /// The destination must either not exist or be an empty, unregistered directory. Any files created by this /// method are removed if adding the worktree fails or is interrupted. /// /// With `extensions.worktreeConfig`, the source's `config.worktree` is copied before checkout, excluding /// `core.worktree` and a true `core.bare` setting. /// The new `HEAD` reflog records its initial commit when `core.logAllRefUpdates` permits it. /// /// `core.sharedRepository` applies to repository metadata, including `HEAD`, its reflog, and the index. /// Like Git when preparing `<destination>/.git`, this also applies shared permissions to newly created /// destination and parent directories, as well as the common `worktrees` directory. Existing directories /// retain their permissions. The private Git directory and checked-out files and subdirectories use normal /// filesystem permissions, including the umask. As a deviation from Git, the linking files `.git`, `gitdir`, /// `commondir`, and `locked` also receive shared permissions. /// /// `worktree.useRelativePaths` selects relative links instead of the default absolute links. When enabled, /// the shared config is upgraded to repository format version 1 with `extensions.relativeWorktrees=true`. /// This compatibility marker remains set even if checkout fails, and requires Git 2.48 or newer. /// The parent repository's configuration snapshot is unchanged; call [`reload()`][Self::reload()] to refresh it. pub fn add_worktree<P>( &self, destination: impl AsRef<Path>, head: Head, mut progress: P, should_interrupt: &AtomicBool, ) -> Result<(crate::Repository, gix_worktree_state::checkout::Outcome), Error> where P: NestedProgress, P::SubProgress: NestedProgress + 'static, { let destination = destination.as_ref(); let relative_paths = Worktree::USE_RELATIVE_PATHS .enrich_error(self.config.resolved.boolean(Worktree::USE_RELATIVE_PATHS)) .with_leniency(self.config.lenient_config)? .unwrap_or_default(); let (head_target, commit_id, root_tree_id) = match head { Head::Attached(name) => { if name.category() != Some(gix_ref::Category::LocalBranch) { return Err(Error::NotLocalBranch { name }); } let checked_out = self.checked_out_branches(None).map_err(|err| match err { crate::repository::worktree::CheckedOutBranchesError::WorktreeListing(err) => { Error::WorktreeListing(err) } crate::repository::worktree::CheckedOutBranchesError::OpenWorktreeRepo(err) => { Error::OpenWorktreeRepo(err) } crate::repository::worktree::CheckedOutBranchesError::FollowSymref(err) => Error::FollowSymref(err), })?; if let Some(worktree_dirs) = checked_out.get(&name) { return Err(Error::CheckedOut { name, worktree_dirs: worktree_dirs.clone(), }); } let mut source = self.clone(); source.clear_namespace(); let mut reference = source.find_reference(name.as_ref())?; let commit = reference.peel_to_commit()?; let root_tree_id = commit.tree_id()?.detach(); (gix_ref::Target::Symbolic(name), commit.id, root_tree_id) } Head::Detached(commit_id) => { let root_tree_id = self.find_commit(commit_id)?.tree_id()?.detach(); (gix_ref::Target::Object(commit_id), commit_id, root_tree_id) } }; if should_interrupt.load(Ordering::Relaxed) { return Err(Error::Interrupted); } let main_repo = self.main_repo().map_err(Error::OpenWorktreeRepo)?; let mut registered_destinations = main_repo.workdir().map(Path::to_owned).into_iter().collect::<Vec<_>>(); for worktree in self.worktrees().map_err(Error::WorktreeListing)? { registered_destinations.push(worktree.base().map_err(Error::WorktreeListing)?); } let prepared = gix_worktree::add::prepare( self.common_dir(), destination, gix_worktree::add::Options { relative_paths, shared_repository_permissions: self.config.shared_repository_permissions, }, ) .map_err(Error::Prepare)?; let canonical_destination = std::fs::canonicalize(prepared.work_dir()).map_err(Error::Prepare)?; for registered_destination in registered_destinations { let registered_destination = gix_path::realpath(registered_destination) .map_err(|err| Error::WorktreeListing(std::io::Error::other(err)))?; let same_destination = if registered_destination == prepared.work_dir() { true } else { match std::fs::canonicalize(®istered_destination) { Ok(path) => path == canonical_destination, Err(err) if err.kind() == std::io::ErrorKind::NotFound => false, Err(err) => return Err(Error::WorktreeListing(err)), } }; if same_destination { return Err(Error::DestinationRegistered { destination: destination.to_owned(), }); } } if relative_paths { let mut config = self.config_file_mut(self.common_dir().join("config"))?; let version = Core::REPOSITORY_FORMAT_VERSION .try_into_usize(config.integer(Core::REPOSITORY_FORMAT_VERSION)) .map_err(crate::config::Error::from)? .unwrap_or_default(); if version > 1 { return Err(crate::config::Error::UnsupportedRepositoryFormatVersion { version }.into()); } if version == 0 { // These are Git's grandfathered v0 extensions. Others become significant on upgrade. for section in config.sections_by_name("extensions").into_iter().flatten() { for name in section.value_names() { if section.header().subsection_name().is_some() || !["noop", "preciousobjects", "partialclone", "worktreeconfig"] .iter() .any(|known| name.eq_ignore_ascii_case(known)) { return Err(Error::UnsupportedExtension { name }); } } } config.set_raw_value(Core::REPOSITORY_FORMAT_VERSION, "1")?; } let enabled = Extensions::RELATIVE_WORKTREES .enrich_error(config.boolean(Extensions::RELATIVE_WORKTREES))? .unwrap_or_default(); if version == 0 || !enabled { config.set_raw_value(Extensions::RELATIVE_WORKTREES, "true")?; config.commit()?; } } // Opening the repository requires HEAD. Preserve symbolic targets so conditional configuration // can see the branch; detached HEADs start at null so their first update records initialization. let mut head_contents = Vec::with_capacity(6 + self.object_hash().len_in_hex()); let reflog_mode = match &head_target { gix_ref::Target::Object(_) => { self.object_hash() .null() .write_hex_to(&mut head_contents) .map_err(Error::WriteHead)?; RefLog::AndReference } gix_ref::Target::Symbolic(name) => { head_contents.extend_from_slice(b"ref: "); head_contents.extend_from_slice(name.as_bstr()); RefLog::Only } }; head_contents.push(b'\n'); std::fs::write(prepared.git_dir().join("HEAD"), head_contents).map_err(Error::WriteHead)?; if Extensions::WORKTREE_CONFIG .enrich_error(self.config.resolved.boolean(Extensions::WORKTREE_CONFIG)) .with_leniency(self.config.lenient_config)? .unwrap_or_default() { copy_worktree_config( &self.git_dir().join("config.worktree"), &prepared.git_dir().join("config.worktree"), )?; } let options = self .options .clone() .without_repository_environment_overrides() .open_path_as_is(true); let mut repo = crate::ThreadSafeRepository::open_opts(prepared.git_dir(), options) .map_err(Error::OpenWorktreeRepo)? .to_thread_local(); repo.clear_namespace(); gix_fs::set_shared_repository_permissions( &repo.git_dir().join("HEAD"), repo.config.shared_repository_permissions, ) .map_err(Error::WriteHead)?; // Like clone, initialize a symbolic HEAD's log without dereferencing or updating its branch. repo.edit_reference(RefEdit::update_with_log( "HEAD".try_into().expect("valid reference name"), commit_id, PreviousValue::Any, LogChange { mode: reflog_mode, ..Default::default() }, )) .map_err(Error::InitializeHead)?; let mut index = repo.index_from_tree(&root_tree_id)?; let mut checkout_options = repo.checkout_options(gix_worktree::stack::state::attributes::Source::IdMapping)?; checkout_options.destination_is_initially_empty = true; let mut files = progress.add_child("checkout"); let mut bytes = progress.add_child("writing"); files.init(Some(index.entries().len()), crate::progress::count("files")); bytes.init(None, crate::progress::bytes()); let started = std::time::Instant::now(); let outcome = gix_worktree_state::checkout( &mut index, prepared.work_dir(), repo.objects.clone().into_arc().map_err(Error::OpenArcOdb)?, &files, &bytes, should_interrupt, checkout_options, )?; files.show_throughput(started); bytes.show_throughput(started); if should_interrupt.load(Ordering::Relaxed) { return Err(Error::Interrupted); } index.write(Default::default(), repo.config.shared_repository_permissions)?; prepared.persist().map_err(Error::Persist)?; Ok((repo, outcome)) }} fn copy_worktree_config(source: &Path, destination: &Path) -> Result<(), Error> { let mut config = match gix_config::File::from_path_no_includes(source.to_owned(), gix_config::Source::Worktree) { Ok(config) => config, Err(gix_config::file::init::from_paths::Error::Io { source, .. }) if source.kind() == std::io::ErrorKind::NotFound => { return Ok(()); } Err(err) => return Err(Error::ReadWorktreeConfig(err)), }; if Core::BARE.enrich_error(config.boolean(Core::BARE))?.unwrap_or_default() && let Ok(mut values) = config.raw_values_mut(Core::BARE) { values.delete_all(); } if let Ok(mut values) = config.raw_values_mut(Core::WORKTREE) { values.delete_all(); } config .write_to(&mut std::fs::File::create(destination).map_err(Error::WriteWorktreeConfig)?) .map_err(Error::WriteWorktreeConfig)}
use std::{
path::Path,
sync::atomic::{AtomicBool, Ordering},
};
use gix_features::progress::{NestedProgress, Progress};
use gix_ref::transaction::{LogChange, PreviousValue, RefEdit, RefLog};
use crate::config::{
cache::util::ApplyLeniency,
tree::{Core, Extensions, Worktree},
};
/// The kind of `HEAD` to install in a newly added worktree.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Head {
/// Check out an existing local branch in the default reference namespace.
Attached(gix_ref::FullName),
/// Check out a commit with a detached `HEAD`.
Detached(gix_hash::ObjectId),
}
/// The error returned by [`Repository::add_worktree()`][crate::Repository::add_worktree()].
#[derive(Debug, thiserror::Error)]
#[expect(missing_docs)]
pub enum Error {
#[error(transparent)]
ConfigBoolean(#[from] crate::config::boolean::Error),
#[error("Could not enable relative worktrees in the shared configuration")]
Configure(#[from] crate::config::file_mut::Error),
#[error("Could not set the repository format for relative worktrees")]
SetConfig(#[from] gix_config::file::set_raw_value::Error),
#[error("Could not upgrade the repository format for relative worktrees")]
RepositoryFormat(#[from] crate::config::Error),
#[error("Cannot upgrade repository format with unsupported extension {name:?}")]
UnsupportedExtension { name: String },
#[error("Could not read the source worktree configuration")]
ReadWorktreeConfig(#[source] gix_config::file::init::from_paths::Error),
#[error("Could not write the new worktree configuration")]
WriteWorktreeConfig(#[source] std::io::Error),
#[error("{name:?} is not a local branch")]
NotLocalBranch { name: gix_ref::FullName },
#[error("The local branch {name:?} is checked out in {worktree_dirs:?}")]
CheckedOut {
name: gix_ref::FullName,
worktree_dirs: Vec<std::path::PathBuf>,
},
#[error("The worktree destination {destination:?} is already registered")]
DestinationRegistered { destination: std::path::PathBuf },
#[error("Failed to read or iterate worktree directories")]
WorktreeListing(#[source] std::io::Error),
#[error("Could not open a worktree repository")]
OpenWorktreeRepo(#[source] crate::open::Error),
#[error("Failed to follow a symbolic reference while inspecting worktrees")]
FollowSymref(#[source] gix_ref::file::find::existing::Error),
#[error("The local branch could not be found")]
FindBranch(#[from] crate::reference::find::existing::Error),
#[error("The local branch could not be peeled to a commit")]
PeelBranch(#[from] crate::reference::peel::to_kind::Error),
#[error("The detached target is not an existing commit")]
FindDetachedCommit(#[from] crate::object::find::existing::with_conversion::Error),
#[error("Could not decode the commit")]
DecodeCommit(#[from] gix_object::decode::Error),
#[error("Could not prepare the linked worktree")]
Prepare(#[source] std::io::Error),
#[error("Could not write the linked worktree HEAD")]
WriteHead(#[source] std::io::Error),
#[error("Could not initialize the linked worktree HEAD and its reflog")]
InitializeHead(#[source] crate::reference::edit::Error),
#[error("Could not create an index from the target tree")]
IndexFromTree(#[from] crate::repository::index_from_tree::Error),
#[error(transparent)]
CheckoutOptions(#[from] crate::config::checkout_options::Error),
#[error("Failed to reopen the object database for checkout")]
OpenArcOdb(#[source] std::io::Error),
#[error(transparent)]
Checkout(#[from] gix_worktree_state::checkout::Error),
#[error("Adding the worktree was interrupted")]
Interrupted,
#[error("Could not write the linked worktree index")]
WriteIndex(#[from] gix_index::file::write::Error),
#[error("Could not finish adding the linked worktree")]
Persist(#[source] std::io::Error),
}
impl crate::Repository {
/// Add and check out a linked worktree at `destination` with the given `head`.
///
/// Attached heads must name an existing local branch which isn't checked out or reserved by an ongoing
/// bisect or rebase in any worktree.
/// Reference namespaces are ignored for branch lookup, occupancy checks, and the returned repository, like
/// `git worktree add`. The source repository's namespace is preserved.
/// The destination must either not exist or be an empty, unregistered directory. Any files created by this
/// method are removed if adding the worktree fails or is interrupted.
///
/// With `extensions.worktreeConfig`, the source's `config.worktree` is copied before checkout, excluding
/// `core.worktree` and a true `core.bare` setting.
/// The new `HEAD` reflog records its initial commit when `core.logAllRefUpdates` permits it.
///
/// `core.sharedRepository` applies to repository metadata, including `HEAD`, its reflog, and the index.
/// Like Git when preparing `<destination>/.git`, this also applies shared permissions to newly created
/// destination and parent directories, as well as the common `worktrees` directory. Existing directories
/// retain their permissions. The private Git directory and checked-out files and subdirectories use normal
/// filesystem permissions, including the umask. As a deviation from Git, the linking files `.git`, `gitdir`,
/// `commondir`, and `locked` also receive shared permissions.
///
/// `worktree.useRelativePaths` selects relative links instead of the default absolute links. When enabled,
/// the shared config is upgraded to repository format version 1 with `extensions.relativeWorktrees=true`.
/// This compatibility marker remains set even if checkout fails, and requires Git 2.48 or newer.
/// The parent repository's configuration snapshot is unchanged; call [`reload()`][Self::reload()] to refresh it.
pub fn add_worktree<P>(
&self,
destination: impl AsRef<Path>,
head: Head,
mut progress: P,
should_interrupt: &AtomicBool,
) -> Result<(crate::Repository, gix_worktree_state::checkout::Outcome), Error>
where
P: NestedProgress,
P::SubProgress: NestedProgress + 'static,
{
let destination = destination.as_ref();
let relative_paths = Worktree::USE_RELATIVE_PATHS
.enrich_error(self.config.resolved.boolean(Worktree::USE_RELATIVE_PATHS))
.with_leniency(self.config.lenient_config)?
.unwrap_or_default();
let (head_target, commit_id, root_tree_id) = match head {
Head::Attached(name) => {
if name.category() != Some(gix_ref::Category::LocalBranch) {
return Err(Error::NotLocalBranch { name });
}
let checked_out = self.checked_out_branches(None).map_err(|err| match err {
crate::repository::worktree::CheckedOutBranchesError::WorktreeListing(err) => {
Error::WorktreeListing(err)
}
crate::repository::worktree::CheckedOutBranchesError::OpenWorktreeRepo(err) => {
Error::OpenWorktreeRepo(err)
}
crate::repository::worktree::CheckedOutBranchesError::FollowSymref(err) => Error::FollowSymref(err),
})?;
if let Some(worktree_dirs) = checked_out.get(&name) {
return Err(Error::CheckedOut {
name,
worktree_dirs: worktree_dirs.clone(),
});
}
let mut source = self.clone();
source.clear_namespace();
let mut reference = source.find_reference(name.as_ref())?;
let commit = reference.peel_to_commit()?;
let root_tree_id = commit.tree_id()?.detach();
(gix_ref::Target::Symbolic(name), commit.id, root_tree_id)
}
Head::Detached(commit_id) => {
let root_tree_id = self.find_commit(commit_id)?.tree_id()?.detach();
(gix_ref::Target::Object(commit_id), commit_id, root_tree_id)
}
};
if should_interrupt.load(Ordering::Relaxed) {
return Err(Error::Interrupted);
}
let main_repo = self.main_repo().map_err(Error::OpenWorktreeRepo)?;
let mut registered_destinations = main_repo.workdir().map(Path::to_owned).into_iter().collect::<Vec<_>>();
for worktree in self.worktrees().map_err(Error::WorktreeListing)? {
registered_destinations.push(worktree.base().map_err(Error::WorktreeListing)?);
}
let prepared = gix_worktree::add::prepare(
self.common_dir(),
destination,
gix_worktree::add::Options {
relative_paths,
shared_repository_permissions: self.config.shared_repository_permissions,
},
)
.map_err(Error::Prepare)?;
let canonical_destination = std::fs::canonicalize(prepared.work_dir()).map_err(Error::Prepare)?;
for registered_destination in registered_destinations {
let registered_destination = gix_path::realpath(registered_destination)
.map_err(|err| Error::WorktreeListing(std::io::Error::other(err)))?;
let same_destination = if registered_destination == prepared.work_dir() {
true
} else {
match std::fs::canonicalize(®istered_destination) {
Ok(path) => path == canonical_destination,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => false,
Err(err) => return Err(Error::WorktreeListing(err)),
}
};
if same_destination {
return Err(Error::DestinationRegistered {
destination: destination.to_owned(),
});
}
}
if relative_paths {
let mut config = self.config_file_mut(self.common_dir().join("config"))?;
let version = Core::REPOSITORY_FORMAT_VERSION
.try_into_usize(config.integer(Core::REPOSITORY_FORMAT_VERSION))
.map_err(crate::config::Error::from)?
.unwrap_or_default();
if version > 1 {
return Err(crate::config::Error::UnsupportedRepositoryFormatVersion { version }.into());
}
if version == 0 {
// These are Git's grandfathered v0 extensions. Others become significant on upgrade.
for section in config.sections_by_name("extensions").into_iter().flatten() {
for name in section.value_names() {
if section.header().subsection_name().is_some()
|| !["noop", "preciousobjects", "partialclone", "worktreeconfig"]
.iter()
.any(|known| name.eq_ignore_ascii_case(known))
{
return Err(Error::UnsupportedExtension { name });
}
}
}
config.set_raw_value(Core::REPOSITORY_FORMAT_VERSION, "1")?;
}
let enabled = Extensions::RELATIVE_WORKTREES
.enrich_error(config.boolean(Extensions::RELATIVE_WORKTREES))?
.unwrap_or_default();
if version == 0 || !enabled {
config.set_raw_value(Extensions::RELATIVE_WORKTREES, "true")?;
config.commit()?;
}
}
// Opening the repository requires HEAD. Preserve symbolic targets so conditional configuration
// can see the branch; detached HEADs start at null so their first update records initialization.
let mut head_contents = Vec::with_capacity(6 + self.object_hash().len_in_hex());
let reflog_mode = match &head_target {
gix_ref::Target::Object(_) => {
self.object_hash()
.null()
.write_hex_to(&mut head_contents)
.map_err(Error::WriteHead)?;
RefLog::AndReference
}
gix_ref::Target::Symbolic(name) => {
head_contents.extend_from_slice(b"ref: ");
head_contents.extend_from_slice(name.as_bstr());
RefLog::Only
}
};
head_contents.push(b'\n');
std::fs::write(prepared.git_dir().join("HEAD"), head_contents).map_err(Error::WriteHead)?;
if Extensions::WORKTREE_CONFIG
.enrich_error(self.config.resolved.boolean(Extensions::WORKTREE_CONFIG))
.with_leniency(self.config.lenient_config)?
.unwrap_or_default()
{
copy_worktree_config(
&self.git_dir().join("config.worktree"),
&prepared.git_dir().join("config.worktree"),
)?;
}
let options = self
.options
.clone()
.without_repository_environment_overrides()
.open_path_as_is(true);
let mut repo = crate::ThreadSafeRepository::open_opts(prepared.git_dir(), options)
.map_err(Error::OpenWorktreeRepo)?
.to_thread_local();
repo.clear_namespace();
gix_fs::set_shared_repository_permissions(
&repo.git_dir().join("HEAD"),
repo.config.shared_repository_permissions,
)
.map_err(Error::WriteHead)?;
// Like clone, initialize a symbolic HEAD's log without dereferencing or updating its branch.
repo.edit_reference(RefEdit::update_with_log(
"HEAD".try_into().expect("valid reference name"),
commit_id,
PreviousValue::Any,
LogChange {
mode: reflog_mode,
..Default::default()
},
))
.map_err(Error::InitializeHead)?;
let mut index = repo.index_from_tree(&root_tree_id)?;
let mut checkout_options = repo.checkout_options(gix_worktree::stack::state::attributes::Source::IdMapping)?;
checkout_options.destination_is_initially_empty = true;
let mut files = progress.add_child("checkout");
let mut bytes = progress.add_child("writing");
files.init(Some(index.entries().len()), crate::progress::count("files"));
bytes.init(None, crate::progress::bytes());
let started = std::time::Instant::now();
let outcome = gix_worktree_state::checkout(
&mut index,
prepared.work_dir(),
repo.objects.clone().into_arc().map_err(Error::OpenArcOdb)?,
&files,
&bytes,
should_interrupt,
checkout_options,
)?;
files.show_throughput(started);
bytes.show_throughput(started);
if should_interrupt.load(Ordering::Relaxed) {
return Err(Error::Interrupted);
}
index.write(Default::default(), repo.config.shared_repository_permissions)?;
prepared.persist().map_err(Error::Persist)?;
Ok((repo, outcome))
}
}
fn copy_worktree_config(source: &Path, destination: &Path) -> Result<(), Error> {
let mut config = match gix_config::File::from_path_no_includes(source.to_owned(), gix_config::Source::Worktree) {
Ok(config) => config,
Err(gix_config::file::init::from_paths::Error::Io { source, .. })
if source.kind() == std::io::ErrorKind::NotFound =>
{
return Ok(());
}
Err(err) => return Err(Error::ReadWorktreeConfig(err)),
};
if Core::BARE.enrich_error(config.boolean(Core::BARE))?.unwrap_or_default()
&& let Ok(mut values) = config.raw_values_mut(Core::BARE)
{
values.delete_all();
}
if let Ok(mut values) = config.raw_values_mut(Core::WORKTREE) {
values.delete_all();
}
config
.write_to(&mut std::fs::File::create(destination).map_err(Error::WriteWorktreeConfig)?)
.map_err(Error::WriteWorktreeConfig)
}