use bstr::{BStr, ByteSlice};
use crate::{stack::State, PathIdMapping};
#[derive(Default, Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Statistics {
pub num_mkdir_calls: usize,
pub push_element: usize,
pub push_directory: usize,
pub pop_directory: usize,
}
pub(crate) struct StackDelegate<'a, 'find> {
pub state: &'a mut State,
pub buf: &'a mut Vec<u8>,
#[cfg_attr(not(feature = "attributes"), allow(dead_code))]
pub is_dir: bool,
pub id_mappings: &'a Vec<PathIdMapping>,
pub objects: &'find dyn gix_object::Find,
pub case: gix_glob::pattern::Case,
pub statistics: &'a mut super::Statistics,
}
impl<'a, 'find> gix_fs::stack::Delegate for StackDelegate<'a, 'find> {
fn push_directory(&mut self, stack: &gix_fs::Stack) -> std::io::Result<()> {
self.statistics.delegate.push_directory += 1;
let dir_bstr = gix_path::into_bstr(stack.current());
let rela_dir_cow = gix_path::to_unix_separators_on_windows(
gix_glob::search::pattern::strip_base_handle_recompute_basename_pos(
gix_path::into_bstr(stack.root()).as_ref(),
dir_bstr.as_ref(),
None,
self.case,
)
.expect("dir in root")
.0,
);
let rela_dir: &BStr = if rela_dir_cow.starts_with(b"/") {
rela_dir_cow[1..].as_bstr()
} else {
rela_dir_cow.as_ref()
};
match &mut self.state {
#[cfg(feature = "attributes")]
State::CreateDirectoryAndAttributesStack { attributes, .. } | State::AttributesStack(attributes) => {
attributes.push_directory(
stack.root(),
stack.current(),
rela_dir,
self.buf,
self.id_mappings,
self.objects,
&mut self.statistics.attributes,
)?;
}
#[cfg(feature = "attributes")]
State::AttributesAndIgnoreStack { ignore, attributes } => {
attributes.push_directory(
stack.root(),
stack.current(),
rela_dir,
self.buf,
self.id_mappings,
self.objects,
&mut self.statistics.attributes,
)?;
ignore.push_directory(
stack.root(),
stack.current(),
rela_dir,
self.buf,
self.id_mappings,
self.objects,
self.case,
&mut self.statistics.ignore,
)?
}
State::IgnoreStack(ignore) => ignore.push_directory(
stack.root(),
stack.current(),
rela_dir,
self.buf,
self.id_mappings,
self.objects,
self.case,
&mut self.statistics.ignore,
)?,
}
Ok(())
}
#[cfg_attr(not(feature = "attributes"), allow(unused_variables))]
fn push(&mut self, is_last_component: bool, stack: &gix_fs::Stack) -> std::io::Result<()> {
self.statistics.delegate.push_element += 1;
match &mut self.state {
#[cfg(feature = "attributes")]
State::CreateDirectoryAndAttributesStack {
unlink_on_collision,
attributes: _,
} => create_leading_directory(
is_last_component,
stack,
self.is_dir,
&mut self.statistics.delegate.num_mkdir_calls,
*unlink_on_collision,
)?,
#[cfg(feature = "attributes")]
State::AttributesAndIgnoreStack { .. } | State::AttributesStack(_) => {}
State::IgnoreStack(_) => {}
}
Ok(())
}
fn pop_directory(&mut self) {
self.statistics.delegate.pop_directory += 1;
match &mut self.state {
#[cfg(feature = "attributes")]
State::CreateDirectoryAndAttributesStack { attributes, .. } | State::AttributesStack(attributes) => {
attributes.pop_directory();
}
#[cfg(feature = "attributes")]
State::AttributesAndIgnoreStack { attributes, ignore } => {
attributes.pop_directory();
ignore.pop_directory();
}
State::IgnoreStack(ignore) => {
ignore.pop_directory();
}
}
}
}
#[cfg(feature = "attributes")]
fn create_leading_directory(
is_last_component: bool,
stack: &gix_fs::Stack,
is_dir: bool,
mkdir_calls: &mut usize,
unlink_on_collision: bool,
) -> std::io::Result<()> {
if is_last_component && !is_dir {
return Ok(());
}
*mkdir_calls += 1;
match std::fs::create_dir(stack.current()) {
Ok(()) => Ok(()),
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
let meta = stack.current().symlink_metadata()?;
if meta.is_dir() {
Ok(())
} else if unlink_on_collision {
if meta.file_type().is_symlink() {
gix_fs::symlink::remove(stack.current())?;
} else {
std::fs::remove_file(stack.current())?;
}
*mkdir_calls += 1;
std::fs::create_dir(stack.current())
} else {
Err(err)
}
}
Err(err) => Err(err),
}
}