use std::io::Write;
use gix_object::{FindExt, bstr::BStr};
use gix_error::{ResultExt, message};
use crate::{AdditionalEntry, SharedErrorSlot, Stream, entry, entry::Error, protocol};
pub fn from_tree<Find, E>(
tree: gix_hash::ObjectId,
objects: Find,
pipeline: gix_filter::Pipeline,
attributes: impl FnMut(&BStr, gix_object::tree::EntryMode, &mut gix_attributes::search::Outcome) -> Result<(), E>
+ Send
+ 'static,
) -> Stream
where
Find: gix_object::Find + Clone + Send + 'static,
E: std::error::Error + Send + Sync + 'static,
{
let (stream, mut write, additional_entries) = Stream::new();
std::thread::spawn({
let slot = stream.err.clone();
move || {
if let Err(err) = run(
tree,
objects,
pipeline,
attributes,
&mut write,
slot.clone(),
additional_entries,
) {
{
let mut slot = slot.lock();
if slot.is_none() {
*slot = Some(err);
} else {
drop(slot);
write.channel.send(Err(std::io::Error::other(err.into_error()))).ok();
}
}
}
}
});
stream
}
fn run<Find, E>(
tree: gix_hash::ObjectId,
objects: Find,
mut pipeline: gix_filter::Pipeline,
mut attributes: impl FnMut(&BStr, gix_object::tree::EntryMode, &mut gix_attributes::search::Outcome) -> Result<(), E>
+ Send
+ 'static,
out: &mut gix_features::io::pipe::Writer,
err: SharedErrorSlot,
additional_entries: std::sync::mpsc::Receiver<AdditionalEntry>,
) -> Result<(), Error>
where
Find: gix_object::Find + Clone,
E: std::error::Error + Send + Sync + 'static,
{
let mut buf = Vec::new();
let tree_iter = objects
.find_tree_iter(tree.as_ref(), &mut buf)
.or_raise(|| message("Could not find a tree to traverse"))?;
if pipeline.driver_context_mut().treeish.is_none() {
pipeline.driver_context_mut().treeish = Some(tree);
}
let mut attrs = gix_attributes::search::Outcome::default();
attrs.initialize_with_selection(&Default::default(), Some("export-ignore"));
let mut dlg = traverse::Delegate {
out,
err,
pipeline,
attrs,
objects: objects.clone(),
fetch_attributes: move |a: &BStr, b: gix_object::tree::EntryMode, c: &mut gix_attributes::search::Outcome| {
attributes(a, b, c).or_raise(|| gix_error::message!("Could not query attributes for path \"{a}\""))
},
path_deque: Default::default(),
path: Default::default(),
buf: Vec::with_capacity(1024),
};
gix_traverse::tree::breadthfirst(
tree_iter,
gix_traverse::tree::breadthfirst::State::default(),
&objects,
&mut dlg,
)
.or_raise(|| message("Could not traverse tree"))?;
for entry in additional_entries {
protocol::write_entry_header_and_path(
entry.relative_path.as_ref(),
&entry.id,
entry.mode,
entry.source.len(),
out,
)
.or_raise(|| message("Could not write entry header"))?;
#[allow(clippy::unused_io_amount)]
match entry.source {
entry::Source::Memory(buf) => out.write(&buf).map(|_| ()),
entry::Source::Null => out.write(&[]).map(|_| ()),
entry::Source::Path(path) => {
let file = std::fs::File::open(path).or_raise(|| message("Could not open file for streaming"))?;
protocol::write_stream(&mut buf, file, out)
}
}
.or_raise(|| message("Could not write entry data"))?;
}
Ok(())
}
mod traverse;